在本课程中,我们将学习加州大学圣地亚哥分校数据科学微硕士项目的第一门课程——Python数据科学。我们将了解课程目标、教学方式以及将要使用的核心工具。
我是Yo K Altonto,圣地亚哥超级计算机中心的首席数据科学官。
我是Leo Porter,加州大学圣地亚哥分校计算机科学与工程系的助理教学教授。
我们很高兴向您介绍加州大学圣地亚哥分校数据科学微硕士项目的第一门课程——《Python数据科学》。在本课程中,您将学习全球数据科学家使用的前沿工具。
这些工具包括 Python、NumPy、Pandas、Jupyter Notebooks、scikit-learn 以及更多其他工具。
然而,您对数据科学产生兴趣,可能不仅仅是因为想学习一系列数据分析工具。您更可能是希望通过数据来更好地理解世界的某些方面。
正因如此,我们不会像其他课程或教科书那样孤立地学习这些Python工具。对于大多数主题,我们将引导您在探索和分析真实世界数据的过程中学习这些工具。
这意味着您将通过实践数据科学来学习数据科学。而实践数据科学正是我们所有人都为之兴奋的事情。
我们投身于这个领域,是因为我们希望看到数据如何改善应急响应人员的行动,帮助我们理解人类活动对环境的影响,或者如何为客户提供个性化服务。这些都是众多研究中的一部分。
课程开始时,您将学习进行数据科学的流程,然后立即开始使用Python工具处理数据集。随着课程的深入,您还将听到来自工业界和学术界的专家分享他们如何在职业生涯中应用数据科学。
我们期待在课程中与您一同学习。现在,让我们开始吧。
在本节课中,我们一起学习了《Python数据科学》课程的导论部分,了解了课程的教学理念——通过实践学习,以及将要接触的核心Python工具和真实数据应用场景。
在本节课中,我们将了解本课程的两位讲师——Leo Porter和Ilkay Altintas——的背景,并通过他们分享的研究项目实例,初步认识数据科学在计算机架构和教育等领域的实际应用。
上一节我们介绍了课程的基本信息,本节中我们来认识第一位讲师。我是Leo Porter,是加州大学圣地亚哥分校计算机科学与工程系的助理教授。
我的专业背景是计算机架构,这是一个专注于处理器高层设计与性能的计算机科学领域。在该领域,我的研究旨在使处理器运行更快、能效更高,或同时实现这两个目标。
目前,我将大部分时间用于研究学生如何学习计算机科学,以及如何改善他们的学习过程。我的工作主要侧重于通过采用一种名为“同伴教学”的循证教学实践来提升学生的学习成果。此外,我也致力于识别学生常见的误解,并对学生的学习成效进行有意义的评估。
在这两个研究领域中,我都运用了数据科学和机器学习的技能来指导并开展研究。以下是两个具体的例子。
以下是来自计算机架构领域的例子:
我们希望能够预测一个程序在具备特定架构特征的高性能计算集群上的运行情况。具体来说,我们想知道启用或禁用某个特定的硬件优化,哪个选择对运行该软件更有利。
为了解决这个问题,我们收集了大量程序的统计数据,并利用机器学习构建了一个性能预测模型。随后,我们可以将单个程序的统计数据输入该模型,模型便会输出该程序在该系统上可能表现的预测。事实证明,对于我们试图解决的这个问题,模型的预测相当准确。
以下是来自计算机科学教育领域的例子:


我们想探究是否可以利用数据来预测哪些学生有课程不及格的风险。在加州大学圣地亚哥分校的许多课程中,我们使用如图所示的课堂应答器(clicker)。学生在学期中使用应答器回答问题,我们想知道这些回答数据能否告诉我们谁可能面临不及格的风险。
我们拥有的数据是:每个学生整个学期的应答器回答记录,以及他们的期末考试成绩。与架构研究的例子类似,我们将这些数据输入一个旨在预测学生学习成果的机器学习模型。然后,我们取一门新课最初几周的应答器回答数据,将其输入预测模型,便能识别出哪些学生可能遇到困难。同样,该预测模型相当准确,使得教师有可能对处于风险中的学生进行干预。
对于这项工作以及之前的研究,我想强调计算机科学研究通常是一项高度协作的工作,并感谢在这些项目中的合作者们。同时,也感谢大家让我介绍我所从事的工作类型。
认识了Leo Porter的研究后,我们再来认识另一位讲师。大家好,我是Ilkay Altintas。和Leo一样,我想向大家介绍一下我在圣地亚哥超级计算机中心和加州大学圣地亚哥分校的工作。
你可能会想,超级计算机中心与数据科学有什么关系?让我来告诉你。正如你可能听说的,在大数据时代,数据科学正在催生许多令人兴奋的不同应用。
我是SDSC的首席数据科学官,领导我们的协作数据科学中心活动。同时,我也领导我们的研究、开发和教育部门,负责监督许多激动人心的研究项目。此外,我热衷于担任“数据科学工作流研究中心”的负责人,该中心是我自2001年加入加州大学圣地亚哥分校SDSC以来,逐步建立并专注的研究领域。
在我的一些教育活动中,我是加州大学圣地亚哥分校“数据科学与工程高级研究硕士”项目的联合主任,并在该项目中教授一门顶点项目课程。我也在计算机科学与工程系担任讲师,并作为其他在线和线下课程项目的一部分,讲授大数据相关课程。
所有这些角色的共同点是,我从事跨学科研究。作为我核心研究、开发和教学活动的一部分,我致力于构建方法论和工具,使大数据、数据科学和计算科学能够有效地服务于动态的、数据驱动的科学应用。在这些领域,我与加州大学圣地亚哥分校的许多中心合作。
我和同事们致力于应对科学和工程所有领域的许多重大数据科学应用挑战,包括基因组学、地理信息学、气象数据科学或智慧城市、能源管理、生物医学、个性化健康等,这些都是我们数据科学中心工作的一部分。所有这些应用的共同点是,它们以独特的方式将新的数据模式和计算研究模式结合在一起。因此,这一切都关乎协作。而超级计算机中心为此带来的,正是计算和数据管理的专业知识。
我日常的工作是思考如何让一群非常聪明的人协作解决数据科学挑战。我关注诸如“我们如何将数据科学方法、工具、计算与数据系统以及领域专家形成一个闭环?”等问题。这些问题也被我的研究小组转化为数据科学的研究挑战和工具箱。我们的主要目标是开发方法和工具,以在大数据和高性能计算平台上构建自动化、可操作、由工作流驱动的解决方案架构。这适用于许多科学学科。
以下是一个我们参与的典型项目实例:

这是一个关于野火分析的项目,分为两个主要部分:预测和应急响应。
WiFIRE是一个由美国国家科学基金会资助的协作项目,旨在构建一个用于野火监测、预测和恢复的网络基础设施。这是一个带来了非常有价值见解的研究项目。
在WiFIRE中,我们构建了一个可扩展的网络基础设施,能够利用任何高端计算、云和大数据平台,进行动态的、大数据驱动的火灾建模与预测。该方法的核心是使用实时数据来了解火灾行为和环境动态,并利用数据科学技术将我们学到的东西同化到火灾模型中,以适应随时间变化的情况。在这里,数据科学方法和工作流被用于系统集成和动态应用扩展。
所有被使用的数据、模型和计算系统在WiFIRE项目之前就已存在,但缺乏一种能够匹配应用需求的可编程系统集成方案。数据科学使得这样的计算能力能够为火灾响应、研究和规划社区所用。事实上,该系统已被一些消防部门用作态势感知工具,我们对此感到非常高兴。
WiFIRE代表了一类广泛的应用,即实时大数据可以与建模和仿真工具相结合,以实现更好的态势感知和动态决策支持。试想一下,数据科学在未来将如何帮助消防工作:许多数据流将汇聚在3D显示界面中,能够同时展示所有相关信息以及天气和火灾预测,这将是数据科学产生社会影响的绝佳应用。毋庸置疑,如果没有许多个人的协作,这一切都不可能实现,这正显示了跨学科协作对数据科学的重要性。
希望你们喜欢了解我的工作。我期待在接下来的10周里与你们分享更多令人兴奋的数据科学应用案例。
本节课中,我们一起认识了本课程的两位讲师Leo Porter和Ilkay Altintas。通过他们分享的在计算机架构性能预测、教育风险学生识别以及野火预测与应急响应系统等领域的实际项目,我们看到了数据科学和机器学习技术如何解决现实世界中的复杂问题,并深刻理解了跨学科协作在数据科学项目中的核心价值。这些实例为我们后续深入学习Python数据科学工具和方法提供了具体的背景和动力。
在本节课中,我们将为您概述这门课程的结构、目标以及帮助您成功的各项安排。

上一节我们介绍了课程的基本信息,本节中我们来看看课程的核心目标。我们为您设定了一个主要目标,这是一个宏大的目标。
我们希望到课程结束时,您能够找到一个开放的数据集,并运用本课程所学的工具进行探索。在探索之前或探索过程中,提出一个能够通过该数据集解答的有意义的研究问题。接着,进一步探索或分析数据,以找到您的研究问题的答案。根据您的发现,您应该能够准确地呈现您的结果。
这听起来可能有些艰巨,但只要您坚持完成课程,您将为进行此类数据分析做好充分准备。事实上,您的期末项目正是要求您完成这项任务。
为了了解我们将如何帮助您做好准备,让我们看看您将在课程中做些什么。
以下是我们的周度概览:
- 第一周:我们将从数据科学和大数据领域的介绍开始。
- 可选周:如果您的背景语言不是Python,或者您对Unix系统经验不足,这一周将为您提供在课程中取得成功所需的背景知识。
- 第二、三周:这两周将专注于在Jupyter笔记本中使用NumPy和pandas处理和操作数据。您将能够读取数据、清理和组织数据,并探索数据。
- 第五周:本周全部关于数据可视化。我们将向您介绍数据可视化领域的一些核心概念,并向您展示如何使用Matplotlib在笔记本中生成可视化图表。
- 第六周:届时您已经掌握了许多关于如何分析数据和呈现结果的知识,因此下一步是花时间在一个小型Jupyter笔记本项目中,使用我们已经展示过的数据集,亲自进行数据分析。
- 第七、八周:这两周将向您介绍数据科学中更高级的主题。第一周侧重于机器学习和scikit-learn库;下一周您将深入学习处理来自网络和数据库的文本,以及使用自然语言处理工具包进行基本的自然语言处理。
- 最后两周:这两周将通过我刚才提到的期末项目,将所有内容整合在一起。

了解了课程安排后,我们来看看如何确保您能顺利完成课程并取得成功。
首先,我们真诚地希望您能完成课程。我们热爱数据科学,并希望您能像我们一样学会欣赏这个领域。我们也知道你们中的许多人时间紧张。为了帮助您保持进度并取得成功,课程中安排了多种活动,包括视频、讨论和练习题。这些活动将为您完成课程提供学分,以激励您不断取得进展。
我们还想让您练习在Jupyter笔记本中工作,因此您会发现带有内置测试的练习笔记本,以帮助您获得关于代码的反馈。在大多数周结束时,您会找到一个分级测验,该测验将基于练习题和练习笔记本的内容。如果您一直在课程中取得进展,您应该能在这些测验中取得成功。但考虑到每个人偶尔都会有状态不佳的时候,您可以放弃最低分的那次测验成绩。
Leo已经谈到了项目,这些项目对于测试您的新技能至关重要。最后,当您完成了课程中的所有其他内容后,您将参加一次期末考试,以获得关于您对课程最终理解的反馈。如果您参与了课程中的所有活动,我们相信您能够取得成功。

最后还有一个关键点。我们希望得到您的反馈,并且会定期征求。我们两人都有构建在线课程的经验,我们知道课程在首次推出后总会有需要调整的地方。我们致力于解决您发现的任何问题,并在您遇到困难时为您提供所需的帮助。因此,请不要犹豫,随时给我们反馈。


本节课中我们一起学习了本课程的核心目标、详细的周度安排以及为确保您成功而设计的各种学习活动和评估方式。我们感谢您加入本课程,并请您查阅接下来的阅读材料以获取更多关于教学大纲的详细信息。
在本节课中,我们将学习数据科学的定义、其重要性以及数据科学家所需的技能。我们将探讨数据科学如何将数据转化为可执行的洞察力,并了解Python在这一过程中的核心作用。
数据科学可以被视为实证研究的基础,数据被用来为我们的假设提供信息并进行观察。在许多情况下,这些数据被企业或科学家用来理解现象。由于通常存在大量可供挖掘洞察的数据,我们常称之为“大数据”。“洞察”是我们用来指代数据科学产出的术语,它通过结合探索性数据分析和建模,从多样化的数据中提取而来。
我们提出的问题有时非常具体,但有时需要查看数据及其模式才能提出具体问题。另一个重要观点是,数据科学不是一次性的静态分析。它涉及一个过程:我们生成的模型会带来洞察,而这些洞察又通过收集进一步的实证证据(即数据)来改进。
例如,像Amazon.com这样的图书零售商,可以利用客户人口统计数据、其过往购买记录和之前的书评,不断改进客户图书偏好模型。他们的模型还可能考虑客户之间的相似性以发现共同兴趣。零售商还可以利用这些信息来预测哪些客户可能喜欢某本电子书,并采取行动向这些客户推销。这就是我们将洞察转化为行动的地方。
正如图书营销示例所示,利用数据科学以及对过去和当前信息的分析,数据科学能够生成行动。这不仅是对过去的分析,更是对未来可执行信息的生成,我们称之为“预测”,它与更好的预测非常相似。当你根据天气预报决定穿什么时,你就是在基于提供给你的洞察采取行动。同样,商业领袖和决策者基于其数据科学团队提供的证据采取行动。
你可能最近才从媒体和雇主那里更多地听到数据科学和对数据科学家的需求,因此数据科学似乎凭空出现。然而,数据科学已经存在很长时间了。科学家们一直使用数据来获得基于观察的洞察。那么,为什么数据科学突然兴起?答案在于两点。
首先,我们实时收集数据的能力随着数据来源的多样化而急剧膨胀,包括实时环境传感器、网站、智能手机和各种其他来源。反过来,这种数据涌入增加了对大规模数据处理的需求。这种数据增长,结合存储、网络和大规模计算的进步,将我们带入了数据科学的新时代。
在这个新时代,许多动态的数据驱动应用程序都建立在数据驱动的预测之上,以支持决策,就像我们讨论的亚马逊图书预测示例。如今,几乎不可能找到一个不受数据科学影响的行业、科学学科或工程项目。只需看看智慧城市、精准医疗、能源管理和智能制造的主要趋势,就能了解它如何塑造当今的经济。所有这些领域都在寻找精通高级数据分析与传统建模和模拟相结合的人才。
我们一开始就说我们收集的数据比以往任何时候都多,但我们谈论的数据量到底有多大?形式如何?让我们来看看。
数据可以包括网站上的用户偏好和购买历史、来自远程传感器和仪器的科学数据、来自可穿戴设备的个人健康数据、与客户满意度相关的社交媒体数据、政治趋势、健康流行病、执法和恐怖活动,以及来自药物试验、治疗方案和患者群体的医疗数据。
这听起来已经像是很多数据了。但我们可以换个角度看。如果你只看互联网上的一分钟,我们就会开始充分理解每分钟产生和存储的数据的庞大规模。
- 每分钟发送2.04亿封电子邮件。
- 每分钟上传20万张照片,Facebook上产生180万个点赞。
- 在YouTube上,每分钟观看278万个视频,上传72小时的视频。
科学数据也不例外。HPN(高性能无线研究与教育网络)仅连接圣地亚哥、河滨和帝王县的传感器,每年就收集30太字节的数据。我们使用从圣地亚哥县各地气象站收集的HPN数据进行野火监测和建模,这包括来自18个站点的每日数吉字节的环境传感器数据和4吉字节的相机数据。
这听起来可能不多,但这只是三个县的一个系统。NASA的MODIS(中分辨率成像光谱仪)是一颗卫星,其成像仪器安装在名为Aqua和Terra的两颗卫星上。这些卫星上的MODIS仪器每1-2天捕获一次整个地球表面的图像,获取36个光谱波段的数据,这相当于40种科学产品,每天产生600吉字节的数据,即每年219太字节的数据。
精准医疗领域也类似。精准医疗的关键前景之一是利用个体的基因图谱来指导有关疾病预防、诊断和治疗的决策。基因组测序只是数据的一部分,还需要用治疗数据、病史和其他生物医学数据来补充。根据2016年《快公司》的一篇文章,仅被诊断患有癌症的人的基因组序列预计就将达到4艾字节,这是非常庞大的数据量。
科学研究中的其他大容量数据源来自LIGO、深空网络和蛋白质数据库。LIGO(激光干涉引力波天文台)是2016年引力波发现的数据源,它提供了用于探测宇宙引力波的大规模物理和天文台。深空网络是NASA的大型天线和通信站点网络,位于多个国家,用于支持太空任务以及研究小行星和行星,它每五秒更新一次其实时数据存储。另一个研究产品是蛋白质数据库,这是一个关于大型生物分子3D结构信息的存储库,对于人类健康和疾病研究以及药物开发非常重要。
管理和分析此类科学数据集是现代科学研究的巨大挑战。在那里,你听到我说了以拍、艾甚至泽开头的词来定义大小。但这到底意味着什么?
作为比较:
- 100兆字节可以容纳几本百科全书。
- 一张DVD大约5吉字节。
- 1太字节可以容纳大约300小时的高质量视频。
目前,以数据为导向的企业收集的数据量级为太字节,但拍字节正变得越来越常见于我们的日常生活。欧洲核子研究中心的大型强子对撞机每年产生15拍字节的数据。根据由一家名为EMC的大数据公司赞助的IDC报告,到2020年,数字数据将增长44倍,从2009年的0.8泽字节增长到35.2泽字节(1泽字节是1万亿吉字节,即10的21次方)。其影响将是巨大的。想想存储和理解如此大量数据所需的所有时间、成本和能源。下一个时代将是尧字节(10的24次方)和Bronot字节(10的27次方),这在目前对我们大多数人来说真的很难想象。这也正是我们所说的天文尺度的数据。
圆圈中间的银河系图像选择不仅仅是为了美观。如果我们将尺度放大10的21次方倍到宇宙中,我们就会看到这样的景象。很酷,不是吗?归根结底,所有这些来源都指向数据量和存储的指数级增长。
虽然我们许多人对大数据带来的机遇感到兴奋,但这种快速增长也带来了一些管理和分析挑战,其中至少包括信息过载。我们的挑战不仅仅是管理数据,还要尝试看清一切是如何连接的。找到我们讨论过的各类数据集之间的联系,有可能带来有趣的发现。这样的努力需要正确使用数据管理、数据驱动方法、用于动态协调和可扩展执行的可扩展工具,以及一支熟练的跨学科劳动力队伍。
这就是你发挥作用的地方。通过投入时间学习Python编程、统计学、机器学习和大数据方面的技能,你将准备好应对数据科学中的一些技术挑战,如药物有效性分析、犯罪模式检测和自动驾驶汽车。
总而言之,数据科学团队通常会聚集在一起,分析商业或科学中单个人无法解决的情况或问题。解决方案有很多活动部分,但最终,所有这些部分都应该结合起来,提供基于数据科学的可执行洞察。现在,能够在决策中使用基于证据的洞察比以往任何时候都更重要。这个微硕士项目将为你提供Python编程、统计分析、机器学习和大数据工具方面的相关技术技能,以实现这一目标。我和Leo期待为你提供整个微硕士项目中都将使用的基础Python数据分析技能。
上一节我们介绍了数据科学家所需的技术技能。但数据科学家整体是什么?为什么数据科学需要Python?让我们在本视频中进一步分析这些内容。
你可能见过这样的图表来描述数据科学。数据科学发生在计算机科学、数学以及商业或科学专业知识的交叉点。如果我们放大这个图表并打开这些专业知识集合,我们会看到这个图形的变体。即使在这个层面,所有这些方框都需要在领域专业知识、数据工程、统计学和计算等领域有更深的知识和技能。根据数据科学职位列表对这些技能进行更深入的分析,会引导你找到机器学习、统计建模、关系代数、商业热情、问题解决和数据可视化等技能。
对于单个人来说,这需要很多技能。考虑到数据科学家定义的多样性,拥有如此广泛的技能似乎是不可能的。有些人甚至开始质疑数据科学家是否像独角兽一样,意味着他们不存在。我想指出的是,确实有数据科学专家在不止一项技能上拥有专长,但他们相对罕见,并且可能仍然需要某些领域专家的帮助。所以实际上,数据科学家是像一个人一样行动的团队。这就是为什么我们说数据科学是团队运动,指的是实现它所需的信息和技能的广度。
然而,数据科学家仍然有一些共同点。例如,数据科学家对数据背后的故事和意义充满热情。他们理解他们试图解决的问题,并旨在找到正确的分析方法来解决这个问题。他们都对设计工程解决方案来解决问题感兴趣。他们也对彼此的工作充满好奇,并拥有在团队内互动以及向他人展示自己的想法和结果的沟通技巧。这些主要是软技能,对于任何数据科学团队的成功都非常重要。在本课程中,我们将重点为你提供技术技能,特别是一些编程和数据分析技能,但也会不时提醒你这些软技能。
但是你应该选择什么数据工具?根据KDnuggets最近一篇基于Indeed.com技能和工作数据的文章,Python在许多数据科学类别中都是明显的领导者。虽然学习这里显示的任何编程语言,包括R、Java、C、Scala和Julia,都是个好主意。但我们选择Python以及雇主寻找这些技能有具体原因。
与其解释为什么Python是数据科学的好语言,不如让我们关注为什么数据科学家喜欢Python。除了是一种易于学习和阅读的语言外,Python还是一种拥有活跃社区的开源语言。得益于这个社区的努力,它提供了不断增长的数据管理、分析处理和可视化库,其中一些我们将在本课程中介绍。这些库使Python适用于数据科学过程的每一步。最后但同样重要的是,Jupyter Notebook使基于Python的分析更具可重复性和可重复性,并提供内置的培训和沟通支持,以帮助团队沟通。
在本课程的其余部分,我们将学习一些最强大的Python库,并将它们应用于从简单的足球数据分析到天体物理学和卫星图像分析的案例研究。我们将从学习Jupyter Notebook开始,然后是NumPy和pandas,以高效地摄取和分析数据。我们将添加可视化库,包括Matplotlib。然后继续应用机器学习库scikit-learn来创建模型。我们将添加像Beautiful Soup这样的库来轻松读取XML和HTML类型的数据,并介绍一些使用数据库的示例。我们希望你会像我们一样享受这段技术编程和学习之旅。
我们刚刚讨论了数据科学为何重要及其一些应用。在本周剩下的时间里,我们将以结构化的方式研究数据科学的不同阶段。在我们深入这个过程之前,让我们通过一个实际例子来了解数据科学是如何运作的。使用真实的数据集进行自己的分析并得出自己的结论要吸引人得多。我们将使用欧洲足球数据并对其进行分析。虽然现在如何构建此示例的某些部分对你来说还不太清楚,但到本课程结束时,实现类似的事情应该是你的目标。
在本视频结束时,你将能够通过一个足球案例研究来讨论数据科学的大局。生成关于足球数据集的统计数据。总结如何将数据清理和相关性应用于现有数据集。复述本研究中使用的数据可视化技术。解释聚类相似组和绘制这些聚类如何帮助案例研究。并回忆基于此数据分析得出了什么结论。
在本课程中,每周我们都会使用一个真实的数据集并处理一个案例研究。本周,我们将使用来自流行网站Kaggle的开放数据集。这个欧洲足球数据库包含2008年至2016年欧洲职业足球赛季的25,000多场比赛和10,000多名球员。尽管在我们的示例中不会深入细节,但该数据集甚至包含每周比赛更新、球队阵容和详细比赛事件的属性。
我们将使用这些数据集来演示我们为此类数据科学项目所采取过程的基本步骤,并将该数据集用于三个主要目标:形成有意义的球员分组、发现与你最喜欢的运动员相似的其他球员,以及通过分析组建强大的球队。既然我们在数据科学中寻找要解决的问题,我们可以围绕这些目标制定问题。例如,我可以说:我如何形成有意义的球员分组,以找到与我喜欢的球员相似的球员?最后,我如何使用这些信息组建强大的球队?
要进一步深入,我们需要问自己:为什么我想了解强大的球队?或者使用分析有什么好处?事实上,你可以问自己的最关键问题之一是:我为什么在这个问题上做数据科学?我期望得到什么洞察?问题会引导你找到你正在寻找的东西,你可以根据你的目标设计策略。我们的目标是获取数据,并从中生成我们可以用来采取数据驱动行动的洞察。我们称这些洞察为“可执行的洞察”,它们非常有价值,并且需要了解主题领域或业务领域。
对于足球场景,我们试图生成的洞察与更好地理解球员优势、表现提升和球员表现属性有关。我们可以将其转化为一个问题:我想找到提高我最喜欢的球员表现的最快方法,我如何知道哪些特质比其他特质更能影响球员的表现?然后,教练可以利用这些洞察并采取行动,设计基于这些洞察的计划来提高球队实力。
那么,我们如何采取步骤从数据到那些最令人垂涎的洞察?在数据科学的整体过程中有五个关键步骤,即数据获取、数据准备、数据分析、洞察的呈现和报告,以及将这些洞察转化为数据驱动的行动。
在我们的足球示例中,获取涉及下载数据集或将其导入到你的数据科学环境中。获取步骤会引导至数据探索和可视化以及其他数据准备。然后,我们使用统计分析和机器学习分析准备好的数据集。这些分析的结果通常会转化为报告,并呈现给需要采取行动的决策者。决策者将利用这些洞察去采取行动并使用它们。
总的来说,当我们浏览示例时,你会注意到任何数据科学项目中都有类似的步骤,就像你从家开车去杂货店的步骤一样:拿钥匙、开门、选择交通工具(汽车、公交车、自行车)、移动、到达、如果需要则停车等等。每个步骤的具体细节可能不同,但整体过程大致相同。本周剩下的时间,我们将把数据科学过程视为一种可推广的活动,但让我们首先继续结合足球数据分析示例来看这些步骤。
作为任何数据科学活动的第一步,我们需要考虑到数据可以来自许多不同的来源。随着更多创新的出现,数据源的多样性只会继续增长。广泛的类别包括:关系和NoSQL数据库、各种数据格式的文本文件,以及来自机器、传感器和在线活动的实时在线流。
在我们的足球示例中,数据提供者从许多互联网站点收集了分散的数据,并进行了大量的数据收集和处理,以使数据准备好进行分析。数据包括关于比分、阵容、球队阵型和事件的结构化数据,以及关于赔率、球员和球队属性的数据。在这种情况下,我们要做的就是获取该数据集并将其摄取到Python中。数据摄取将是我们本课程的重点领域之一。Python有定义良好的方法,可以从不同的资源摄取数据。这些来源包括各种数据库、数据访问API(如Twitter API)、文本文件以及传感器数据流。到本课程结束时,你将知道如何有效地利用这些数据源中的每一个。
我们数据科学过程的下一步是探索数据集。Python拥有可以在数据准备阶段帮助你探索数据集的库,例如,只需一行命令,如你在这里看到的,你就可以生成数据集的重要统计摘要,如平均值和标准差。
数据准备还涉及数据清理,因为现实世界的数据集存在许多挑战。清理也可以建立在统计分析的基础上,比如移除异常值、缺失值,或者一般来说,从数据中剔除不需要的东西。尽管有时移除不需要的条目可能是一个快速的解决方案,但有时决定移除什么仍然是一个挑战。在这些情况下,你可以用已知的聚合值(如列的平均值等)来填充那些字段。Python提供了数据清理功能来帮助完成一般的数据清理任务,如查找和移除空值。在足球数据分析的示例笔记本中,我们分享了一些入门示例,但我们将通过本课程中每个数据科学过程步骤的案例研究来指出这些功能。
在数据科学过程的每一步,数据可视化都是吸引团队注意力并在最短时间内传达信息的有效方式。Python有几个开源的数据可视化库,可以使这项任务对我们来说容易得多。在接下来的几周里,我们有一个专门的可视化部分。
一旦基本准备步骤完成,分析就是数据科学的核心。你开始接触算法。虽然我们的微硕士项目的第三门课程将带你深入了解这些算法,但在第一门课程中,我们将向你介绍其中的一些。有三个关键类别:监督学习、无监督学习和半监督学习。有大量的算法和技术,如本图中所示的降维、聚类和回归。例如,Python中的Scikit-learn库为机器学习和Python提供了许多工具。作为第一门课程的一部分,我们将向你介绍其中一些工具。在这里,我们开始给你一些使用这些工具的例子,作为足球数据分析和我们即将进行的案例研究的一部分。
作为此示例的一部分,我们进行特征选择。特征选择是关于选择对你正在解决的问题影响最大的属性。它需要一些领域知识来缩小特征的数量。例如,在足球用例中,如果你试图预测球员的表现,哪些是最关键的属性?是关于敏捷性、反应时间、射门力量和冲刺速度的蓝色属性,还是关于发型或电影偏好的绿色属性?同样,如果你将球员分组到不同的集合中,你会选择哪些属性来分配这些分组以创建复杂的特征?
缩小特征范围有几个好处:你得到的模型更容易解释;模型训练速度更快;并且你更有可能很好地泛化到新场景。你会发现几乎所有顶级的机器学习算法都已经在Python中实现了。不同的功能被组织到Python的库中,如scikit-learn,其中包含基本机器学习算法的实现。在足球示例中,我们将利用一种称为K-means的聚类算法,它来自scikit-learn。聚类意味着根据你刚刚决定的那些属性,将你的球员分组到相似且有意义的集合中。这张幻灯片的目的是向你展示,你可以多么快速简洁地调用Python中子程序来完成你想要做的事情。导入Python中用于K-means的正确库,然后我们使用该库分析我们的数据。注意这里聚类是如何仅用一行代码完成的。现在还不用担心这些方法的细节。尽管选择正确的分析工具需要进一步的专业知识和理解,但本课程将为你打下基础,以便在后续课程中继续构建。
现在让我们回到我们为足球案例研究遵循的数据科学过程。此时,我们已经完成了聚类。这意味着我们已经根据我们选择的属性将球员分组到有意义的组中。接下来,我们将开始解释结果。那么,我们如何分析结果?需要考虑的是:所有组都有相同数量的球员吗?当我们查看我们选择的属性时,这些组有何不同?绘制这些聚类可能有助于解释和呈现这些结果。这里的图表对我们两者都有帮助。
一旦我们完成了数据清理、分析和解释的所有工作,就是时候展示我们的发现了。演示或报告的一个重要部分是解释我们如何解释这些结果。看看图表,将这四条线视为我们K-means算法根据本图x轴所示特征找到的四个聚类中每一个的特征签名。有任何一组具有完全相同的特征签名吗?答案是没有。每组都是独特的,因为它在至少一个属性上与其他三组不同。为了根据这些发现采取行动,球队教练可以利用这些信息为每个组设计定制的改进策略。
有许多技术和**实践用于呈现或可视化这些结果。我们需要决定图表类型,找到一个库或自己编写,为图片提供足够的细节以使其不言自明,比如添加轴标签、图例和可读的字体大小。
总而言之,我们刚刚解释了一个足球数据分析案例研究,以及我们如何使用五步数据科学过程从原始数据集中生成洞察。该过程涉及获取结果、数据准备、分析以及结果报告,然后可用于数据驱动的行动。我们解释了为什么可执行的洞察是任何数据科学项目的预期产物,以及利用数据和领域知识来生成我们可以通过数据科学回答的问题的重要性。我们还讨论了Python为数据科学提供的一些工具。
作为本周材料的一部分,我们给你一个完整的Jupyter Notebook,展示了我们的足球数据分析示例。到本课程结束时,你将能够为自己简单的数据分析任务创建类似的笔记本。接下来,在我们开始深入研究Unix和Python脚本的细节之前,我们将把这个过程推广到任何数据科学问题。
Python数据科学:P6:数据科学如何运作

在本节课中,我们将学习数据科学项目从提出问题到获得洞察的完整流程。我们将探讨数据科学的关键维度,学习如何清晰地定义问题,并详细拆解数据科学过程的五个核心步骤。
现在我们已经明确了数据科学的定义,让我们回到如何利用数据科学从数据中提取价值或解决问题。
数据科学过程是如何从提出问题到回答问题的?换句话说,我们能否将之前数据分析示例中看到的步骤进行归纳,看看数据科学是如何得出洞察的?
通过本视频,你将能够列举现代数据科学的一些维度,并理解分析这些维度对我们作为数据科学家的重要性。
我们构建和观察成功数据科学项目的经验,可以总结为一项包含几个不同组成部分的技艺。这些组成部分可以被定义为数据科学的关键特征或维度。
总结之前的内容,我们将数据科学定义为一门多学科的技艺,它结合了跨学科的团队和特定的应用目的。
一般来说,数据科学始于一个团队、一个宏观的广泛问题,当然,还有一些待探索的数据。可以说,我们从数据和问题开始,并围绕如何得出数据驱动的洞察来构建一个过程。
过程在最初是一个概念实体,它定义了解决问题的核心步骤。然后,这个过程会细化到许多专业领域,步骤之间的界限常常是模糊的。
看待这个过程的方式有很多。一种方式是将它视为两个不同的活动:主要是数据工程和数据分析(或我称之为计算大数据科学),因为在这个阶段通常进行的不仅仅是简单的分析。
更详细地看待这个过程,可以揭示数据科学过程的五个不同步骤或活动,即:获取、准备、分析、报告和行动。
我们可以简单地说,数据科学发生在所有这些步骤的交界处。理想情况下,这个过程应支持在大数据和云平台上的实验性工作和动态扩展。
在现实的大数据应用中,如果我们加上不同工具之间的依赖关系,这个五步过程可以以其他方式使用。大数据的影响推动了在过程的每一步采用可替代的扩展性方法。

另一种看待这个过程的方式是,看到所有这些步骤都有不同形式的报告需求。或者,将所有这些活动视为一个迭代过程,包括针对大数据步骤的构建、探索和扩展。

可扩展的数据分析需要替代性的数据管理技术、系统、分析工具和方法,以及基于动态数据和计算负载的可扩展性节点。物理基础设施的变化,以及由特殊事件引发的流数据特定紧迫性,也需要考虑。
为简化起见,在本课程介绍中,我们将这个过程称为一组按顺序进行并迭代的五项活动。然而,归根结底,可扩展的过程应该能够通过利用可重用和可复现的系统、中间件、分析工具、可视化环境和最终用户报告环境的编程接口来进行编程。
这个最终的维度,即用Python对数据科学步骤进行编程,将是本课程的重点。我们将使用Python模块进行数据分析、数据处理和可视化的示例。
现在,我们将重点放在如何清晰地表述一个数据科学问题上。
通过本视频,你将能够描述构成数据科学问题的要素,列举他人为从数据中获取价值而提出的一些问题,并制定正确的问题来指导你的数据科学过程。
任何过程的第一步都是定义你要解决的问题是什么。需要解决什么问题?或者需要把握什么机会?没有这一点,你就没有明确的目标,也不知道何时解决了问题。
例如,一个问题可能是:如何结合销售数据和呼叫中心日志来评估任何新产品? 或者在制造过程中:如何利用设备上多个传感器的数据来检测设备故障? 再或者:我们如何更好地了解客户和市场,以实现有效的精准营销?
接下来,你需要根据你定义的问题或机会来评估现状。这是一个需要谨慎行事的步骤,需要分析风险、成本、收益、应急措施、法规、资源和情况要求。
问题的要求是什么?假设和约束条件是什么?你可用的资源有哪些?这包括人员和资本(如计算机系统、设备等)。与该项目相关的主要成本是什么?潜在的收益是什么?执行项目存在哪些风险?针对潜在风险的应急措施是什么?回答这些问题将帮助你更好地了解全局,更好地理解项目涉及的内容,以及如何指导你的编程,在考虑所有这些因素的情况下解决项目。
然后,你需要定义你的目标和目的。定义成功标准也非常重要。你希望在项目结束时实现什么?明确的目标和成功标准将帮助你在整个项目生命周期中评估项目。
一旦你知道了要解决的问题,并理解了约束条件和目标,你就可以制定计划来得出答案,即解决你的业务问题或实现你试图达成的分析目标。
总而言之,定义你希望找到答案的问题是任何数据科学项目成功的重要因素。通过遵循上述步骤,你可以制定更好的问题,利用分析技能来解决,并将其与科学和商业价值联系起来。
现在,让我们深入了解数据科学过程的基本步骤。在本课程讨论的所有案例研究中,我们将持续应用这些步骤中的部分或全部。
通过本视频,你将能够识别数据科学过程中的步骤,并理解每个步骤涉及的内容。我们已经看到了数据科学过程的一个简单线性形式,包括五个相互依赖的不同活动。
在深入每个细节之前,让我们进一步总结每一项活动。
获取 包括任何使我们检索数据的活动,包括查找、访问、获取和移动数据。它包括识别和验证所有相关数据的访问权限,将数据从不同来源传输过来,以及将数据子集化并匹配到感兴趣的区域或时间(有时我们称之为地理空间查询)。
我们根据活动的性质,将准备数据分为两个步骤。数据准备的第一步是实际查看数据以了解其性质、含义、质量和格式。这通常需要对数据或数据样本进行初步分析才能理解。因此,这个步骤被称为准备/探索。一旦我们通过探索性分析对数据有了更多了解,下一步就是为分析进行数据预处理。它包括清理数据、子集化或过滤数据,以及通过将原始数据建模为更明确的数据模型或使用特定的数据格式进行打包,来创建程序可以读取和理解的数据。如果涉及多个数据集,此步骤还包括整合来自不同数据源或流的数据。
准备好的数据随后会传递到分析步骤,该步骤涉及选择要使用的分析技术、构建数据模型和分析结果。这个步骤本身可能需要进行几次迭代,或者可能需要数据科学家回到步骤1和2以获取更多数据或以不同方式打包数据。
第四步是沟通结果。它包括评估分析结果,以可视化方式呈现它们,并创建包含针对成功标准的结果评估报告。每个步骤中的活动通常可以用解释、总结、可视化和后处理等术语来指代。
最后一步是将我们带回到最初进行数据科学的目的。报告分析中的洞察,并根据最初定义的目的从洞察中确定行动,这就是我们所说的行动步骤。
我们现在已经看到了典型数据科学过程中的所有步骤。请注意,这是一个迭代过程,一个步骤的发现可能需要根据新信息重复之前的步骤,而这正是数据科学中的“科学”所在。

本节课中,我们一起学习了数据科学项目的运作流程。我们从数据科学的多维度特性开始,探讨了如何清晰地定义和评估一个数据科学问题。随后,我们详细拆解了数据科学过程的五个核心步骤:获取、准备、分析、报告和行动,并理解了它们之间的迭代关系。掌握这个流程是成功开展任何数据科学项目的基础。

在本节课中,我们将系统性地学习数据科学项目的完整流程,从数据采集到最终将洞察转化为行动。我们将了解每个步骤的目标、常用技术以及需要注意的关键问题。
数据科学流程的第一步是获取数据。在本节中,我们将学习如何访问和检索所需数据的技术与工具,并描述一个从多种来源获取数据的示例场景。
数据采集意味着在分析或处理数据之前,需要先获得原始材料。第一步是确定有哪些数据可用。在寻找合适的数据源时,我们需要做到全面无遗漏。我们的目标是识别与问题相关的合适数据,并利用所有相关数据进行分析。有时,即使遗漏一小部分重要数据也可能导致错误的结论。
数据来源广泛,包括本地和远程,形式多样,有结构化和非结构化之分,并且数据流的速度(即数据流的速度)也各不相同。访问这些不同类型的数据有多种技术和工具。
以下是几种常见的数据访问方式:
- 关系型数据库:许多数据存在于传统的关系型数据库中,例如来自组织的结构化数据。访问数据库数据的首选工具是结构化查询语言(SQL),它被所有关系数据库管理系统支持。此外,大多数数据库系统都配有图形化应用程序环境,允许用户查询和探索数据库中的数据。
- 文件:数据也可以存在于文件中,例如文本文件和Excel电子表格。通常使用脚本语言从文件中获取数据。像Python这样的脚本语言是一种高级编程语言,可以是通用型的,也可以专用于特定功能。其他支持文件处理的常见脚本语言包括JavaScript、PHP、Perl、R、Octave和MATLAB等。
- 网站:从网站获取数据是一种日益流行的方法。网页使用万维网联盟(W3C)批准的一套标准编写,包括各种格式和服务。常见的格式有XML(可扩展标记语言) 或JSON,它们都使用标记符号和标签来描述网页内容。许多网站还提供Web服务,以编程方式访问其数据。Web服务有几种类型,最流行的是REST,因为它易于使用。REST代表表述性状态转移,是一种考虑性能、可扩展性和可维护性的Web服务实现方法。WebSocket服务也越来越受欢迎,因为它们允许从网站进行实时通知。
- NoSQL存储系统:NoSQL存储系统越来越多地用于管理各种数据类型。这些数据存储是不以传统关系数据库的列和行表格格式表示数据的数据库。这类数据存储的例子包括Cassandra、MongoDB和HBase。NoSQL数据存储提供API供用户访问数据,这些API可以直接使用,也可以在需要访问数据的应用程序(如Python脚本)中使用。此外,大多数NoSQL系统通过Web接口(如REST)提供数据访问。
示例场景:野火数据分析
在我们圣地亚哥超级计算机中心的一个应用项目中,我们使用野火数据分析来预测火灾方向和蔓延速度。这个项目需要使用几种不同的机制来获取数据。
该项目本身将气象站的历史传感器数据存储在关系数据库中。我们使用SQL从数据库中检索这些数据,以创建模型来识别与火灾天气条件相关的天气模式。为了确定特定气象站当前是否正在经历火灾天气条件,我们使用WebSocket服务访问实时数据。一旦开始监听此服务,我们就能在气象站测量数据发生时接收它们。然后处理这些数据,并与模型发现的模式进行比较,以确定气象站是否正在经历圣安娜风或火灾天气条件。
同时,使用与该地区发生的任何火灾相关的标签来检索推文。推文消息通过Twitter REST服务检索。目的是确定这些推文的情绪,看看人们是表达恐惧、愤怒,还是对附近的火灾漠不关心。传感器数据和推文情绪的结合有助于我们了解火灾情况的紧迫性。
总结:数据可以来自许多地方。在开始获取数据之前,找到并评估所有对我们分析有用的数据非常重要。根据数据的来源和结构,有不同的访问方式,我们将在接下来的Python课程中了解所有这些访问方法。
上一节我们介绍了如何获取数据,本节中我们来看看获取数据后的重要步骤——数据探索。在本节中,您将能够解释探索数据的重要性,并识别对数据进行初步分析的方法。
在收集了应用程序所需的数据之后,您可能会忍不住立即开始构建模型来分析数据。我们必须**这种诱惑。获取数据后的第一步是探索数据,因为探索数据是两步数据准备活动的重要组成部分。您需要进行一些初步调查,以便更好地了解数据的具体特征。在此步骤中,您将寻找诸如相关性、总体趋势、异常值等特征。如果没有这一步,您将无法有效地使用数据。
以下是数据探索中常用的几种方法:
- 相关性图:可用于探索不同变量之间的依赖关系。
- 总体趋势图:向您展示数据随时间变化的简单图表。
- 异常值检测:向您展示远离其他数据点的数据点。绘制异常值将帮助您仔细检查由于测量导致的数据错误。在某些情况下,非错误的异常值可能会让您发现罕见事件。
- 汇总统计:提供描述数据的数值。汇总统计量是用单个数字或一小组数字来捕捉一组值的各种特征的量。您应该为数据集计算的一些基本汇总统计量包括均值、中位数、众数、极差和标准差。均值和中位数是特定值位置的度量。众数是数据集中出现最频繁的值。极差和标准差是数据离散程度的度量。查看这些度量可以让您了解数据的性质。它们可以告诉您数据是否存在问题,例如,如果数据中年龄值的范围包括负数或远大于100的数字,则数据中存在可疑之处,需要检查。Python提供了函数和方法,我们可以快速探索数据并提供这些统计信息,我们将在本课程结束时了解所有这些内容。
可视化技术也提供了快速有效、总体上非常有用的方式来查看数据,在这个初步分析步骤中非常有用。例如,热图可以快速让您了解热点在哪里。
可以使用许多不同类型的图表。直方图显示数据的分布,可以显示偏度或异常离散。箱线图是另一种显示数据分布的图表类型。折线图有助于查看数据中的值如何随时间变化。数据中的峰值也易于发现。散点图可以显示两个变量之间的相关性。总的来说,有许多类型的数据可视化图表,它们在帮助您理解所拥有的数据方面非常有用。展望未来,我们实际上将花费近一周的时间专门关注有效的数据可视化方法。
总结:通过探索数据,您将获得对所处理数据复杂性的更好理解。这反过来将指导流程的其余部分。既然我们在探索数据后对其复杂性有了更多了解,接下来我们将讨论数据预处理或转换,使其为分析做好准备。
上一节我们探讨了数据,本节我们将学习如何对数据进行预处理,使其适合分析。在本节中,您将能够识别现实世界数据的一些问题,并描述将原始数据转换为可用于分析的数据所需的工作。
直接从来源获取的原始数据永远不会是进行分析所需的格式。数据预处理步骤有两个主要目标:第一是清理数据以解决数据质量问题;第二是转换数据以使其适合分析。
数据准备的一个非常重要的部分是解决数据中的质量问题。现实世界的数据是混乱的。来自实际应用的数据存在许多质量问题的例子,包括:
- 不一致的数据:例如,在两个不同销售地点记录的同一客户有两个不同的地址,但这些记录不一致。
- 缺失值:例如,在人口统计研究中缺少客户年龄。
- 无效值:例如,一个六位数的邮政编码。
- 异常值:例如,传感器故障导致一段时间内的值远高于或低于预期。
由于我们是下游获取数据,通常对数据如何收集控制有限。在数据收集时预防数据质量问题通常不是一种选择。因此,我们必须通过检测和纠正来处理我们获得的数据中的质量问题。
以下是一些我们可以用来解决这些数据质量问题的方法:
- 删除缺失值的记录。
- 合并重复记录:这需要一种方法来确定如何解决冲突值。也许在有冲突时保留最新值是合理的。
- 替换无效值:可以使用合理值的**估计作为替换。例如,可以根据对员工雇佣时长的合理估计来填补员工的缺失年龄值。
- 移除异常值:如果它们对任务不重要,也可以移除。
为了有效解决所有这些数据质量问题,关于应用程序的知识(例如数据如何收集、用户群体、应用程序的预期用途等)非常重要。这种领域知识对于就如何处理不完整或不正确的数据做出明智决策至关重要。您还需要小心所做的更改,以避免得出错误的结论,并务必记录所做的更改。
数据准备的第二部分是将清理后的数据操作成分析所需的格式。这一步有许多名称:数据操作、数据预处理、数据整理,可能我最喜欢的是数据混合。这种数据混合、整理、预处理的操作包括缩放、转换、特征选择、降维和数据操作。
让我们进一步详细看看这些操作:
- 缩放:涉及将值的范围更改为指定范围之间,例如从0到1。这样做是为了避免某些具有大值的特征主导结果。例如,在分析身高和体重数据时,体重值的数量级远大于身高值的数量级。因此,将所有值缩放到0到1之间将使身高和体重特征的贡献均等化。
- 转换:可以对数据执行各种转换以减少噪声和变异性。一种这样的转换称为聚合。聚合数据通常会产生变异性较小的数据,从长远来看可能有助于分析。例如,每日销售数据可能有许多虚假变化,将值聚合为每周或每月销售数据将产生更平滑的数据。其他过滤技术也可用于消除数据中的变异性。当然,这是以数据细节减少为代价的,因此必须针对特定应用权衡这些因素。
- 特征选择:可以涉及移除冗余或不相关的特征、组合特征以及创建新特征。在探索数据步骤中,您可能发现两个特征高度相关,在这种情况下,可以移除其中一个特征而不会对分析结果产生负面影响。例如,产品的购买价格和支付的销售税额可能高度相关。那么,消除销售税额将是有益的。移除冗余或不相关的特征将使后续分析更简单。在其他情况下,您可能希望组合特征或创建新特征,例如,在贷款审批申请中添加申请人的教育水平作为特征是有意义的。还有一些算法可以根据各种数学属性自动确定最相关的特征。
- 降维:当数据集具有大量维度时非常有用。它涉及找到一个较小的维度子集,以捕获数据中的大部分变化。这减少了数据的维度,同时消除了不相关的特征,并使分析更简单。一种常用的降维技术称为主成分分析(PCA)。
- 数据操作:原始数据通常需要操作才能成为分析的正确格式。例如,从记录每日股价变化的样本中,我们可能希望捕获特定市场板块(例如房地产或医疗保健)的价格变化。这需要确定哪些股票属于哪个市场板块,将它们分组在一起,并可能计算每个组的均值、极差和标准差。
总结:数据准备是数据科学流程中非常重要的一部分。事实上,在任何数据科学工作中,您将把大部分时间花在这上面。这可能是一个乏味的过程,但却是关键的一步。请始终记住,在数据处理方面,垃圾进,垃圾出。如果您不花时间和精力为分析创建良好的数据,那么无论您的数据分析技术多么复杂,都不会得到好的结果。
现在我们已经准备好了数据,终于可以进行分析了。在本节中,您将能够描述将分析技术应用于数据所涉及的内容,并列出三种基本的分析技术。
现在您已经准备好了数据,下一步是分析数据。数据分析涉及从您的数据(称为输入数据)构建模型。输入数据被分析技术用来构建模型。您的模型生成的是输出数据。有不同类型的问题,因此也有不同类型的分析技术。
分析技术的主要类别包括分类、回归、聚类、关联分析和图分析。我们现在将描述每一种。
- 分类:目标是预测输入数据的类别。一个例子是预测天气为晴天、雨天、有风或多云。在这种情况下,要预测的类别是晴天、雨天、有风和多云。另一个例子是将肿瘤分类为良性或恶性。在这种情况下,由于只有两个类别,该分类被称为二元分类。但您也可以有许多类别,例如这里显示的具有四个不同类别的天气预测问题。另一个例子是将手写数字识别为10个类别(即0到9)之一。
- 回归:当您的模型必须预测数值而不是类别时,任务就变成了回归问题。回归的一个例子是预测股票随时间变化的价格。股票价格是数值,不是类别,因此这是一个回归任务,而不是分类任务。回归的其他例子包括:估计任何新产品的每周销售额,以及预测测试分数。
- 聚类:在聚类中,目标是将相似的项目组织成组。一个例子是将公司的客户群划分为不同的细分市场,以便进行更有效的定向营销,例如老年人、成人和青少年。其他例子包括:为土地利用应用识别具有相似地形(例如山脉、沙漠平原)的区域,以及确定不同的天气模式组(如雨天、寒冷或下雪)。
- 关联分析:目标是提出一组规则来捕捉项目或事件之间的关联。这些规则用于确定项目或事件何时一起发生。关联分析的一个常见应用称为市场篮子分析,用于理解客户购买行为。例如,关联分析可以揭示,拥有定期存款账户(CDs)的银行客户也往往对其他投资工具(如货币市场账户)感兴趣。这些信息可用于交叉销售。如果您向拥有CDs的客户宣传货币市场账户,他们很可能会开设此类账户。根据数据挖掘的传说,一家超市连锁店使用关联分析发现了两种看似无关的产品之间的联系。他们发现,许多在周日深夜去超市购买尿布的顾客也倾向于购买啤酒。然后,这些信息被用来在周日将啤酒和尿布放在一起。他们看到这两种商品的销量都出现了跃升。这就是著名的“尿布与啤酒”关联。
- 图分析:当您的数据可以转换为具有节点和链接的图表示时,您希望使用图分析来分析您的数据。当您拥有大量实体以及这些实体之间的连接时(如社交网络),就会出现这种数据。图分析可能有用的例子包括:通过分析医院和医生记录来探索疾病或流行病的传播;通过监控社交媒体、电子邮件和文本数据来识别安全威胁;以及优化移动通信网络流量以确保数据质量并减少掉话。
建模首先根据您所处理问题的类型,从我们列出的这些技术中选择一种作为适当的分析技术。然后,使用您准备好的数据构建模型。为了验证模型,您将其应用于新的数据样本。这是为了评估模型在用于构建它的数据上的表现。常见的做法是将准备好的数据分为用于构建模型的数据集和保留一些数据用于在模型构建后评估模型。您也可以使用以与构建模型数据相同方式准备的新数据。
模型的评估取决于您使用的分析技术类型。让我们简要看看如何评估每种技术:
- 对于分类和回归:您的输入数据中每个样本都有正确的输出。比较正确输出和模型预测的输出提供了一种评估模型的方法。
- 对于聚类:应检查聚类产生的组,看看它们对您的应用是否有意义。例如,客户细分是否反映了您的客户群?它们对用于定向营销活动是否有帮助?
- 对于关联分析和图分析:需要进行一些调查,看看结果是否正确。例如,需要调查网络流量延迟,看看您的模型预测的是否实际发生,以及延迟的来源是否如预测的那样存在于真实系统中。
在评估了模型以了解其在您数据上的性能后,您将能够确定后续步骤。需要考虑的一些问题包括:是否应该使用更多数据进行分析以获得更好的模型性能?使用不同的数据是否有帮助?例如,在您的聚类结果中,是否难以区分来自不同地区的客户?或者是否需要将邮政编码添加到输入数据中以帮助生成更细粒度的客户细分?分析结果是否表明需要对问题的某些方面进行更详细的研究?例如,预测晴天天气效果很好,但雨天天气预测效果一般。这意味着您应该更仔细地查看雨天天气的样本,也许这些样本中存在一些异常,或者为了完全捕捉雨天天气,需要包含一些缺失的数据。理想的情况是,您的模型在项目开始时定义问题所确定的成功标准方面表现非常好。在这种情况下,您就可以继续沟通并根据分析获得的结果采取行动了。
总结:数据分析涉及为问题选择合适的技术,构建模型,然后评估结果。由于存在不同类型的问题,也存在不同类型的分析技术,我们需要了解这些技术,以确保我们将正确的技术应用于我们的数据集和问题。
上一节我们完成了数据分析,本节我们将学习如何报告从分析中获得的洞察。在本节中,您将能够确定在报告发现时应呈现的内容,并识别沟通结果的技术。
数据科学流程的第四步是报告从分析中获得的洞察。这是沟通您的洞察并为后续应采取的行动提供依据的重要一步。它可以根据您的受众进行调整,不应掉以轻心。
那么,我们如何开始呢?第一件事是查看您的结果并决定呈现什么。这意味着确定您的分析中哪一部分最重要,能为您的科学界、公司、行业或特定受众提供最大价值。
在决定呈现什么时,您应该问自己这些问题:我的重点是什么?换句话说,主要结果是什么?基于我工作的特定领域和引导我提出问题的应用,这些结果提供了什么价值?模型如何增强这个应用?换句话说,结果与项目开始时为该应用特定目的确定的成功标准相比如何?您需要在报告或演示中包含这些问题的答案。因此,让这些问题和答案成为主要主题,并务必有数据或可视化来支持它们。
请记住,并非所有的分析结果都可能是乐观的。这通常很难沟通。您的分析可能显示出与您希望发现的结果相反的结果,或者结果不确定或令人困惑。您也需要展示这些结果。与您合作的领域专家可能会发现其中一些结果令人困惑。不确定的发现会导致额外的分析。请记住,报告发现的目的是确定下一步应该做什么。必须呈现所有发现,以便做出明智的决策。如果您仔细想想,最大的危险是让您的结果看起来讲述了一个清晰的故事,而实际上并非如此。如果您的结论后来被发现是错误的,您的可信度可能会受到严重损害。最好讲述一个完整而真实的故事,即使它不是很清晰,也不要试图粉饰事物,让它们听起来比实际情况更清晰。
可视化是呈现结果的重要工具。我们在探索数据时讨论的技术也可以在这里使用。散点图、折线图、热图和其他类型的图表是直观呈现结果的有效方式。您还应该有包含分析细节的表格作为备份,如果有人想更深入地研究结果。
有许多可用的可视化工具。这里列出了一些最受欢迎的开源工具,包括:R,一个用于数据分析的软件包,具有强大的可视化功能;Python,我们将在本课程中看到,它是一种通用编程语言或脚本语言,允许您使用许多包来支持数据分析和图形;D3,一个用于生成基于Web的交互式可视化和数据驱动文档的JavaScript库;Leaflet,一个轻量级、移动友好的JavaScript库,用于创建交互式地图;最后,Tableau和Google Charts允许您在个人资料中创建可视化,以便分享或将它们放在网站或博客上,并且它们提供跨平台兼容性到移动设备;Timeline是一个JavaScript库,允许您根据这些结果创建时间线。我们将使用与Python的Jupyter笔记本良好连接的工具,并将在即将到来的一周专注于可视化。
总结:您希望通过呈现结果和使用可视化工具来报告您的发现,以帮助您有效地做到这一点。
上一节我们讨论了如何报告结果,本节我们将学习如何将洞察转化为实际行动。在本节中,您将能够解释将洞察转化为行动的含义,并将您的结果与您的业务或科学问题联系起来。
现在您已经评估了分析结果,并就结果的潜在价值生成了报告,下一步是根据获得的洞察确定应采取什么行动。
请记住我们最初为什么要汇集大型数据集进行分析。目标是找到可操作的洞察,以帮助回答科学或商业问题。
例如,在商业中:您的流程中是否存在应该改变以消除瓶颈的问题?是否应该向您的应用添加数据以提高其准确性?或者您是否应该将人群划分为更明确的组以进行更有效的定向营销?同样,在科学中:药物试验的益处是否具有统计学意义?森林砍伐的当前速度是多少?您能预测15年后将剩下多少森林吗?再举一个例子,您可以问这样的问题:您能根据望远镜图像对遥远的行星进行聚类吗?
既然您从数据中获得了一些洞察,下一步就是将其转化为行动。根据您的发现,您现在很可能可以采取一些行动来改善业务、更好地治疗患者或改善环境。现在您需要弄清楚如何实施这些行动,将这一行动添加到您的流程或应用中需要什么。如果可以,应该如何实现自动化?利益相关者需要被识别并参与到这一变革中,就像任何流程改进变革一样。我们需要监控和衡量该行动对流程或应用的影响。务必考虑在变革期间和之后应收集哪些数据以正确评估其影响。对已实施行动结果的评估将决定您的后续步骤。是否需要执行额外的分析以获得更好的结果?应该重新审视哪些数据?是否有其他机会应该探索?
例如,我们不要忘记大数据使我们能够做什么。我们可以基于快速流动的信息采取实时行动。在商业中,我们需要定义业务的哪部分需要实时行动,以便能够影响运营或与客户的互动。在公共服务中,我们需要知道在数据中观察到某些事件时应采取什么行动。一旦我们定义了这些实时行动,我们需要确保在我们的组织或科学研究小组中有自动化系统或流程来执行此类行动,并在出现问题时提供故障恢复。
总结:大数据和数据科学只有在洞察能够转化为行动时才是有用的,并且应该仔细定义和评估行动。


我们现已完成了数据科学流程的描述。在接下来的九周里,我们将使用Python和Jupyter笔记本,在学习可用于数据科学项目的关键Python库的同时,将此流程应用于案例研究。在10周结束时,您可以确信您将能够将这些技能添加到您的简历中。希望您和我们一样享受Python和数据科学的乐趣。

在本节课中,我们将要学习第二周课程的整体安排与目标。本周内容专注于Python和Unix的基础知识,旨在帮助有不同背景的学习者快速上手或巩固知识。

欢迎来到Python数据科学课程的第二周。
本周的重点是Python和Unix的基础知识。对于已经具备这两方面背景的学习者,本周内容完全是可选的。
Python部分专为那些有其他语言编程经验,但需要快速教程以掌握用Python编写程序的学习者设计。
对于那些有一些Python经验,但认为额外帮助仍有价值的学习者,可以自由选择观看哪些视频,或者直接跳到Python课程结束时的可选编程作业。
对于Unix部分,我们将介绍一些在本课程中你可能希望使用的工具。同样,如果你已经熟悉Unix,可以自由跳过。如果你遇到困难,以后随时可以回头参考这些资源。
上一节我们介绍了本周课程的整体定位,本节中我们来看看课程内容的具体构成与学习建议。
以下是针对不同背景学习者的具体学习路径建议:
- 有其他语言编程经验者:建议重点观看Python部分的视频,以快速适应Python语法。
- 有少量Python经验者:可以挑选性地观看视频,或直接通过编程作业进行实践。
- 熟悉Unix系统者:可以跳过Unix部分,或在需要时将其作为参考资料。
本节课中我们一起学习了第二周课程的导论内容,明确了Python和Unix基础部分的学习目标,并了解了根据个人背景选择学习路径的建议。
在本节课中,我们将要学习为什么选择Python作为数据科学的编程语言,并掌握Python编程的基础知识,包括变量、对象、循环、条件语句、函数和作用域。
上一节我们介绍了本课程将使用的Jupyter环境。本节中我们来看看为什么选择Python作为核心编程语言。
Python自1991年诞生以来,已成为全球最流行的编程语言之一。Python官网阐述了其优势:
- 强大:用Python只需几行代码就能完成的任务,在其他语言(如Java或C++)中可能需要三到五倍的代码量。
- 快速:作为一种解释型语言,Python的速度令人惊讶。它通过调用用C或C++编写的高度优化库来实现高性能。
- 兼容性好:Python能很好地与其他语言协作,常被用作“胶水语言”,连接用不同语言编写的组件。
- 可移植:由于是解释型语言,只要有Python解释器,任何Python程序都能运行。
- 易于学习:动态类型和自动内存管理等特性使Python易于学习和阅读。
- 开源:Python是开源的,无需担心许可费用,并且社区活跃。
此外,Python拥有庞大的工具生态系统,可以连接和处理来自不同领域的真实世界数据,这使得掌握Python的数据科学家在就业市场上极具竞争力。
为了展示Python在数据科学中的实际应用,我们来看一个在线教育领域的研究案例。
该研究分析了862个慕课(MOOC)视频的观看日志数据,涉及超过127,000名学生和690万次视频观看会话。研究流程如下:
- 数据提取:使用
PyMongo库从MongoDB数据库中提取数据。 - 数据分析:使用
NumPy库进行数值分析。 - 统计检验:使用
SciPy库进行统计学检验。 - 数据可视化:使用
Matplotlib库生成图表。 - 组织分析:使用Jupyter Notebook组织整个分析流程。
以下是部分核心发现:
- 视频时长与观看时长:6至9分钟的视频,观众平均观看时间较长。超过9分钟的视频,观看时长显著下降。
- 视频完成率:对于6至9分钟的视频,观众观看了大部分内容。而对于9至12分钟或更长的视频,观众观看不足一半甚至仅四分之一。
这些结论已被广泛应用于慕课制作中,现在制作5分钟左右的短视频已成为标准实践。
在上一节中,我们了解了Python的优势。本节中我们开始学习Python编程的基础——变量。
Python代码简洁明了。例如,打印“Hello World”只需一行代码:print("Hello World")。Python语句末尾不需要分号,并且使用缩进而非花括号来定义代码块。
在Python中创建变量非常简单,例如 x = 3。与C++或Java等强类型语言不同,Python是动态类型语言,变量在声明时无需指定类型。
Python支持多种内置数据类型,包括整数(int)、浮点数(float)、列表(list)、字节(bytes)、布尔值(bool)和字符串(str)。

动态类型意味着你可以轻松地改变变量所指向的数据类型,例如:
x = 3 # x 现在是一个整数 x = 4.5 # x 现在是一个浮点数,不会报错

上一节我们介绍了变量的动态类型。本节中我们来探讨其背后的原理:在Python中,一切皆对象。
对象是包含数据(属性)和可执行操作(方法)的实体。即使是像整数这样的基本类型,在Python中也是对象(例如 PyIntObject)。
当我们执行 x = 3 时,Python会在内存中创建一个值为3的 PyIntObject 对象,然后让变量 x 指向这个对象。当执行 x = 4.5 时,会创建一个新的 PyFloatObject 对象,并让 x 指向它,之前无人引用的 PyIntObject 对象会被垃圾回收器自动清理。
要检查两个变量是否指向同一个对象,使用 is 操作符。要检查两个对象的值是否相等,使用 == 操作符。
x = 3 y = 3.0 print(x is y) # 输出: False,指向不同对象 print(x == y) # 输出: True,值相等
我们已经知道Python中一切都是对象。本节中我们学习如何调用对象的方法。
以字符串对象为例,字符串在Python中是不可变的。调用其方法会返回一个新的字符串对象,而不会修改原字符串。
x = "Hello" print(x.upper()) # 输出: HELLO print(x) # 输出: Hello,x本身未改变
要改变变量 x 的值,需要将方法返回的新对象赋值给它:
x = "Hello" x = x.lower() # 创建新对象并让x指向它 print(x) # 输出: hello
方法调用的格式为:变量名.方法名(参数)。
一个常见的误区是关于变量赋值。y = x 并不意味着 y 和 x 永久绑定,它只是让 y 暂时指向 x 当前所指的对象。之后 x 的改变不会影响 y。
x = 7 y = x # y 指向 7 x = 3 # x 指向新的对象 3 print(x, y) # 输出: 3 7
循环是编程中的基本结构。本节中我们学习Python中的 for 循环和 while 循环。


for 循环常与 range() 函数搭配使用。range(start, stop, step) 生成一个从 start 到 stop-1,步长为 step 的数字序列。
for i in range(0, 10, 2): # 从0开始,到9结束,步长为2 print(i) # 输出: 0, 2, 4, 6, 8
while 循环在条件为真时重复执行代码块。
i = 0 while i < 5: print(i) i += 1 # 输出: 0, 1, 2, 3, 4
关于循环条件评估次数的一个关键点:条件检查的次数比循环体执行的次数多一次(最后一次检查结果为 False 时终止循环)。
本节我们学习使用 if、elif 和 else 语句来控制程序分支。
条件语句的基本结构如下:
if 条件: # 条件为真时执行的代码 elif 另一个条件: # 上一个if为假,且此条件为真时执行 else: # 所有以上条件都为假时执行
例如,打印0到4之间的偶数,并将奇数加10后打印:
for i in range(5): if i % 2 == 0: # 检查是否为偶数 print(i) else: # 否则(即为奇数) print(i + 10) # 输出: 0, 11, 2, 13, 4
函数将子任务抽象成独立的代码块。本节中我们学习如何定义和调用Python函数。
使用 def 关键字定义函数。Python是动态类型的,所以无需指定参数和返回值的类型。
def my_abs(v): # 定义函数,参数为 v if v < 0: return -v else: return v print(my_abs(-7)) # 输出: 7
重要概念:Python的参数传递是“按对象引用传递”。函数内对参数重新赋值(使用 =)不会影响外部变量。因为这只是让参数变量指向了一个新对象。
def increment_value(val): val = val + 1 # val 指向了新对象,外部x不变 x = 7 increment_value(x) print(x) # 输出: 7
打印(print)和返回(return)是不同的。函数需要返回值供调用者使用,而打印只是将内容输出到屏幕。
变量的作用域决定了它在程序中的可访问范围。本节中我们学习Python的作用域规则。
在函数内部定义的变量是局部变量,只能在函数内部访问。
def my_func(): local_var = 10 print(local_var) # 错误!NameError: name 'local_var' is not defined
在函数外部定义的变量是全局变量,可以在整个文件(模块)中访问。但在函数内部若要修改全局变量,需要使用 global 关键字声明。不过,过度使用全局变量可能导致代码难以维护,一些编程规范建议避免使用。

本节课中我们一起学习了Python作为数据科学语言的优势、一个实际应用案例,以及Python编程的核心基础:变量与动态类型、对象模型、循环、条件语句、函数和作用域。掌握这些基础知识是后续学习NumPy、Pandas等数据科学库的关键。
在本节课中,我们将学习Python中用于数据科学的核心数据结构与基础库。我们将从字符串操作开始,逐步深入到列表、元组、字典、集合以及列表和字典推导式。掌握这些知识是进行后续数据科学分析的基础。
上一节我们介绍了课程概述,本节中我们来看看Python中处理文本数据的基础——字符串的常用函数。
字符串是Python中用于表示文本的数据类型。Python的字符串库提供了丰富的函数来操作字符串。虽然本节会介绍许多常用函数,但请注意,字符串库中还有更多函数。如果你需要对字符串进行某种操作,建议首先查阅库的文档,很可能已经有现成的函数能满足你的需求。
以下是改变字符串大小写的函数:
lower(): 返回一个新字符串,其中原字符串的所有字符都转换为小写。upper(): 返回一个新字符串,其中原字符串的所有字符都转换为大写。
字符串是不可变的,因此本节讨论的所有函数都会返回新的字符串。
如果你想拼接两个字符串,可以使用连接操作。使用加号 + 可以将两个字符串连接起来。
string1 = “Hello” string2 = “World” result = string1 + “ “ + string2 # 结果为 “Hello World”
如果你想重复一个字符串多次,可以使用复制操作。使用乘号 * 可以复制字符串。
string = “Hi” result = string * 3 # 结果为 “HiHiHi”
你可以结合连接和复制操作来构建更复杂的字符串。
strip() 函数在数据科学中非常有价值。你经常会发现数据值周围有多余的空格或换行符,这些无用空白会影响分析。strip() 函数可以去除这些空白。
text = “ example ” clean_text = text.strip() # 结果为 “example”
你并不总是想去除空格,strip() 函数可以接受一个参数,指定要去除的字符。
text = “10” clean_text = text.strip(“*”) # 结果为 “10”
有时你需要将一个长字符串分割成多个子字符串。split() 函数可以实现这个功能,它会返回一个列表(列表可以容纳多个元素)。
sentence = “This is a sentence” words = sentence.split(“ “) # 结果为 [‘This‘, ‘is‘, ‘a‘, ‘sentence‘]
你也可以用逗号或其他分隔符来分割字符串。
data = “Jane Doe,Cars,5” fields = data.split(“,“) # 结果为 [‘Jane Doe‘, ‘Cars‘, ‘5‘]
通过索引获取字符串的一部分称为切片。我们以单词 “Hello” 为例,其字符索引从0开始。
word = “Hello” sub1 = word[1:3] # 结果为 “el“ (索引1包含,索引3不包含) sub2 = word[4:] # 结果为 “o“ (从索引4到末尾) sub3 = word[-4:-1] # 结果为 “ell“ (负索引从末尾开始计数)
有两种简单的方法可以检查一个字符串是否包含某个子串。第一种是使用 in 关键字。
word = “Hello” print(“HE“ in word) # 输出 False print(“He“ in word) # 输出 True
如果你想找到子串在字符串中的位置,可以使用 find() 方法。它返回子串起始的最低索引,如果子串不存在则返回 -1。
word = “Hello” position = word.find(“el“) # 结果为 1
结合切片使用 find() 会很有用,因为你可以先找到子串的起始位置,然后从那里开始提取字符。
在本课程中,我们既会处理文本数据也会处理数值数据,因此需要知道如何在字符串和数字之间转换。
str_num = “1234” int_num = int(str_num) # 转换为整数 1234 float_num = float(str_num) # 转换为浮点数 1234.0
如果你尝试将非数字文本转换为整数,将会引发错误。
最后,你可能有一个大段文本,并想在其中插入一些字符串。这时可以使用 format() 函数。
template = “I love {} and {}.“ result = template.format(“data“, “analysis“) # 结果为 “I love data and analysis.“
你可以为占位符编号,以更精确地控制替换顺序。
template = “I love {1} and {0}.“ result = template.format(“analysis“, “data“) # 结果为 “I love data and analysis.“
当输入数据的顺序与你想要的字符串顺序不同时,这会非常有用。
本节我们讨论了许多字符串函数。请记住,还有更多函数可以应用于字符串,因此在以特定方式操作字符串时,请务必查阅文档。
在上一节中,我们学习了如何操作字符串。本节中,我们将学习Python中用于存储数据集合的核心数据结构——列表。
列表类似于Java中的ArrayList或C++中的vector。这意味着它是可调整大小的,底层由数组实现。通过本节学习,你将能够使用列表存储数据、使用循环遍历列表,并识别一些常见的列表函数。
让我们从创建一个包含值11、22和33的列表开始。
my_list = [11, 22, 33]
列表元素有索引,和字符串一样,索引从0开始。如果我们请求索引为1的元素,将得到22。
print(my_list[1]) # 输出 22
如果你尝试访问超出列表范围的索引,将会引发错误。
要打印列表中的所有元素,可以使用循环进行遍历。
for item in my_list: print(item)
对于更熟悉C或Java风格循环的人,也可以这样写:
for i in range(len(my_list)): print(my_list[i])
len() 函数返回列表中项目的数量。我们鼓励你使用第一种循环方式,因为它更不易出错且更易读。
与字符串不同,你可以更改列表元素的内容。
my_list[1] = 95 print(my_list) # 输出 [11, 95, 33]
列表是可调整大小的,这意味着我们可以在列表末尾追加值。
my_list.append(44) print(my_list) # 输出 [11, 95, 33, 44]
你可以添加元素,也可以移除元素。pop() 方法通过索引移除元素并返回它。
removed_item = my_list.pop(2) # 移除索引为2的元素(33) print(my_list) # 输出 [11, 95, 44]
remove() 方法通过值移除元素。
my_list.remove(95) # 移除值为95的元素 print(my_list) # 输出 [11, 44]
有时你想合并两个列表。如果合并只是简单地将一个列表添加到另一个列表的末尾,可以使用 extend() 方法。
list1 = [1, 2, 3] list2 = [4, 5, 6] list1.extend(list2) print(list1) # 输出 [1, 2, 3, 4, 5, 6]


注意不要混淆 append() 和 extend()。append() 将一个元素(即使是列表)添加到末尾,而 extend() 将一个列表的所有元素添加到另一个列表中。
你经常需要同时处理两个列表,这时可以使用 zip() 函数。
list1 = [1, 2, 3] list2 = [‘a‘, ‘b‘, ‘c‘] for x, y in zip(list1, list2): print(x, y) # 输出 1 a, 2 b, 3 c
我们刚刚介绍了列表的常用方法,但还有更多。例如,你可以像切片字符串一样切片列表。更多示例请查阅文档。
接下来我们做一个快速测验。问题是:以下代码会输出什么?
x = [10, 20, 30] y = x x[1] = 42 print(y)
答案是 [10, 42, 30]。因为 y = x 使得 y 和 x 指向内存中的同一个列表对象。列表是可变的,所以通过 x 修改列表也会影响 y。
如果你希望 y 是 x 的一个独立副本,需要显式地创建副本:
y = list(x)
知道何时创建副本或使用相同引用可能会困扰经验丰富的程序员,但如果你放慢速度并画一个简单的内存图,就能快速发现错误。
在上一节视频中,我们学习了如何使用列表存储数据集合。列表是可变的,这意味着你可以随时更改其元素。在本节中,我们将学习元组。
元组是一种组合数据的方式,但由于其不可变性,在数据分析的许多情况下很有价值。你不需要了解很多关于元组的细节就能有效使用它们,因此这将是一个相当简短的介绍。通过本节学习,你将知道如何创建元组并执行一些基本的元组操作。
让我们从创建一个元组开始。元组通常用于将某些方面相关联的信息组合在一起。
car_info = (“Honda“, “Civic“, 4, 2017)
和列表一样,可以通过索引访问元素。
print(car_info[0]) # 输出 “Civic“ print(len(car_info)) # 输出 4
也可以使用循环遍历元组。
for item in car_info: print(item)
与列表不同的是,如果你尝试更改元组,会引发错误。
car_info[3] = 2018 # 这行代码会引发 TypeError
这看似不便,但实际上非常重要。元组的不可变性意味着你可以信任它永远不会改变。
不可变性在两个方面至关重要。第一个更通用,涉及并行计算。在并行计算中,可以更改的数据会使并行计算变得更困难,因为必须确保处理问题的每个人都看到这些更改。但如果数据不能更改,你可以将其副本发送到计算节点,而无需担心它在计算过程中发生变化。
我们关心的第二个原因更具体,实际上适用于我们的下一个视频:元组可以用作字典的键。因为元组不会改变,字典可以根据其初始值进行组织,而无需担心持有其引用的任何人更改键。
现在我们了解了元组,接下来可以学习字典了。
在本节视频中,我们将学习Python中的字典。字典是我最喜欢的数据结构之一。通过本节学习,你将知道如何创建和使用字典,识别常见的字典陷阱,并理解无序集合的固有局限性。
字典本质上就是Python对“映射”的术语。如果你使用过Java或C++,可能接触过Map接口,并且很可能使用哈希映射实现。字典存储键值对的组合。
student_dict = {“A123“: “Alice“, “B456“: “Bob“}
键必须是不可变类型,而值可以是任何对象,包括列表或其他字典。
course_instructors = {“CSE101“: [“Dr. Smith“, “Prof. Lee“]} movie_ratings = {(“Ghostbusters“, 1984): 7.8, (“Ghostbusters“, 2016): 5.4}
让我们看看如何创建和使用字典。
movies = {(“Ghostbusters“, 1984): 7.8, (“Ghostbusters“, 2016): 5.4} print(movies)
使用键来查找值。
rating = movies[(“Ghostbusters“, 1984)] # 结果为 7.8 print(len(movies)) # 输出 2
向字典中添加新条目很简单。
movies[(“Cars“, 2006)] = 7.1 print(movies)
字典是无序集合,其内部项目没有固有的顺序,并且你不能依赖其内部顺序保持不变。这种无序性是字典提供快速性能的部分原因。如果你的问题需要以某种方式保持顺序,字典可能不是理想选择。
使用括号访问键的值时需小心。如果键不存在,会引发运行时错误。
更安全的方法是使用 get() 方法或 in 关键字。
# 使用 get() rating = movies.get((“Toy Story“, 1995)) # 键不存在,返回 None # 使用 in 关键字 if (“Toy Story“, 1995) in movies: rating = movies[(“Toy Story“, 1995)]
可以使用 pop() 方法移除字典中的项,它会返回被移除键对应的值。
removed_rating = movies.pop((“Ghostbusters“, 2016)) # 返回 5.4
也可以使用 del 语句,如果你不关心返回值。
可以遍历字典的键、值或键值对。
# 遍历键 for key in movies: print(key) # 遍历键值对 for key, value in movies.items(): print(key, value)
不能在迭代字典时修改它(添加或删除项),否则会引发运行时错误。这是一个普遍的不良习惯。
如果你需要遍历字典并根据条件删除某些项,可以分两步完成:首先收集要删除的键,然后删除它们。
to_remove = [] for key in movies: if key[1] < 2000: # 如果电影年份早于2000年 to_remove.append(key) for key in to_remove: movies.pop(key)
总而言之,字典是Python中一个非常棒的数据结构。在本课程后续部分,我们将使用一些专门为数据科学优化的数据结构,因此你可能不会像单独使用Python编程那样频繁地使用字典。但我相信你仍然会发现它们非常方便。
现在我们已经学习了列表和字典,我们将看到Python实际上允许我们以一种称为“推导式”的简单方式来构建它们。通过本节学习,你将能够使用推导式构建列表和字典。
推导式是快速构建列表或字典的一种方式。假设我想构建一个包含1到10的平方的列表。
squares = [i2 for i in range(1, 11)] # 结果为 [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
for i in range(1, 11) 部分会为 i 提供值1到10。i2 部分是表达式,表示我们想要每个 i 值的平方。
让我们看更多例子:
# 0到5的列表 list1 = [i for i in range(6)] # [0, 1, 2, 3, 4, 5] # 0到20之间的偶数 evens = [i for i in range(0, 21, 2)] # [0, 2, 4, ..., 20] # 10个0和1交替的列表 alt = [i % 2 for i in range(10)] # [0, 1, 0, 1, ...] # 10个0到5之间的随机整数 import random rand_list = [random.randint(0, 5) for _ in range(10)]
你也可以使用推导式来构建字典。主要区别在于现在需要同时指定键和值。
# 创建一个字典,键为数字,值为对应的字母 letter_dict = {i: chr(i) for i in range(65, 91)} # {65: ‘A‘, 66: ‘B‘, ..., 90: ‘Z‘}
chr() 函数将ASCII码转换为对应的字符。
我想简要介绍一下Python中的集合。集合是一个有用的数据结构,因为它们支持许多有用的数学运算,并且只允许唯一的元素。通过本节学习,你将能够在Python中创建集合并使用集合操作。
集合有三个对我们有用的特性。首先,和字典一样,它们是无序的。这使得它们对于许多关键操作非常快速。其次,它们包含唯一元素,这意味着不能有重复值。第三,它们支持有用的集合操作,包括并集和交集。
让我们通过一些例子看看集合为何方便。
# 创建集合 my_colors = set([“blue“, “green“, “red“]) # 添加元素 my_colors.add(“yellow“) # 移除元素 my_colors.discard(“green“) # 如果元素不存在,discard不会报错 # my_colors.remove(“purple“) # 如果元素不存在,remove会引发KeyError
创建两个集合:
my_colors = {“blue“, “green“, “red“} ichi_colors = {“blue“, “yellow“}
并集给出两个集合中所有的唯一元素。
all_colors = my_colors.union(ichi_colors) # 结果为 {‘blue‘, ‘green‘, ‘red‘, ‘yellow‘} # 或使用 | 运算符 all_colors = my_colors | ichi_colors
交集给出两个集合中共有的元素。
common_colors = my_colors.intersection(ichi_colors) # 结果为 {‘blue‘} # 或使用 & 运算符 common_colors = my_colors & ichi_colors
我想让你从本节中学到的主要一点是:如果你试图找出两组元素之间共有的唯一元素,或者想要合并两组以找出所有唯一元素,集合是一个非常有用的数据结构。
本节课总结

在本节课中,我们一起学习了Python数据科学的核心基础。我们从字符串的常用操作函数开始,了解了如何改变大小写、连接分割、查找替换以及格式化字符串。接着,我们深入探讨了Python的几种核心数据结构:可变的列表用于存储有序集合,不可变的元组用于安全地打包数据,灵活高效的字典用于存储键值对映射,以及用于处理唯一元素和集合运算的集合。最后,我们学习了列表和字典推导式,这是一种快速生成这些数据结构的简洁语法。掌握这些字符串操作方法和数据结构是进行有效数据清洗、处理和建模的基石,为后续更复杂的数据科学任务做好了准备。

在本节课中,我们将要学习Unix操作系统的基础知识。Unix命令行及其提供的工具是任何数据科学项目的核心工具之一。在开始使用它们进行后续讲座和练习之前,我们将为您提供一个基于Unix的操作系统的基本介绍。
您可能会惊讶地发现,当今大多数操作系统都构建在Unix之上。除了基于Windows操作系统的那些,几乎所有操作系统都是如此。各种Linux发行版、macOS、iOS和Android只是当今流行的几个例子。
下图展示了基于Unix的操作系统的家族树,仅此图就显示了Unix的影响力。毋庸置疑,这使得Unix在工业界被广泛采用。事实上,许多数据和计算系统的后端,包括像Facebook和Google这样的行业巨头,都使用Unix。
作为一个操作系统,Unix提供了一个非常强大的开发环境,它建立在许多小型实用程序的可组合性之上,每个程序都专注于做好一件事。我们称这些程序为命令。用户可以使用这些命令和一些编程语法(或简称为脚本)来构建自己的命令和程序。
Unix无疑是大多数编程工作的必备技能。那么,为什么它对我们数据科学家来说很重要呢?除了作为一个强大的操作系统,Unix还提供了用于数据搜索、子字符串提取和转换的命令。有效使用这些命令可以帮助通过命令行快速操作和分析数据,这在探索性分析和数据准备阶段尤其有用。此外,Unix甚至提供了将命令链接在一起作为简单管道的快速方法。许多数据科学工具都带有命令行界面,这需要与命令行交互并通过Unix shell执行。在拥有一些Unix命令行经验后,您可能会发现更容易使用其他命令行工具和应用程序。
我们将从了解Unix操作系统的三个主要部分开始,即内核、shell和程序。
Unix shell是用户和内核之间的接口,它充当Unix的命令行解释器。简单来说,它允许内核执行Unix命令和其他用户开发的程序。例如,在这个shell中,ls 是一个用于列出文件的Unix命令。它将被shell解释,并在用户按下回车键时执行。
Unix shell在用户每次登录到类Unix系统时自动启动。它接受命令并为这些命令执行系统调用。它还通过shell环境提供了一个编程或shell脚本接口。
总结一下,在Unix中,命令是由shell执行的程序。每次shell从用户那里接收到一个命令时,它会将该命令传递给内核,内核进而创建一个具有唯一标识符的进程来运行它。
Unix的一个重要特性是,一切都被表示为一个进程或一个文件,甚至是硬盘卷。这就是为什么Unix没有像Windows那样的命名驱动器的概念。
- 进程:是一个正在执行的程序,由一个唯一的进程标识符(我们通常称之为PID)来标识。
- 文件:是数据的集合,并组织在一个目录结构中。用户和正在运行的进程创建这些文件。目录实际上只是包含其他文件的特殊文件。
文件被组织在一个分层结构中,看起来像一棵倒置的树,有一个根。为了访问和处理每个文件,我们通过这个树形结构中的所谓路径。路径用斜杠字符分隔我们接触到的树的每个节点。我们可以从根开始,经过由斜杠分隔的每个目录,到达每个文件,这被称为绝对路径。
例如,/users/altas/f.txt 就是文件 f.txt 的一个绝对路径。使用这个绝对路径,我可以在我的Unix系统中的任何地方访问到 f.txt。
另一种访问 f.txt 的方法是尽可能使用相对路径。如果我已经在我的主目录中(我的主目录是 /users/altas,在这里用绿色表示),我可以直接访问该目录下的 f.txt,而无需列出其他目录名。这被称为相对路径,因为它相对于我们正在操作的目录。我们将正在操作的目录称为工作目录。
因此,以 /users/altas 作为工作目录(用绿色表示),我可以说 f.txt 或 ./f.txt 来访问 f.txt。这里的 . 指的是工作目录。
现在,假设我的工作目录是 python_for_data_science(用黄色表示),我想访问 altas 下的 f.txt。我可以选择将工作目录更改为 altas,然后使用我们之前用过的相对路径。或者,我可以使用 f.txt 的绝对路径。然而,Unix提供了一个更简单的方法来访问 f.txt。我们将利用 .. 目录,它指的是我们当前目录的父目录。每个Unix目录都有一个特殊的实用程序,指向该目录的父目录。对于 python_for_data_science,其父目录是 altas。父目录用两个点 .. 表示,我们可以通过 ../f.txt 从 python_for_data_science 访问 f.txt。
现在,我想向您展示Unix系统中的另一个重要快捷方式:波浪号 ~ 字符。/users 下的任何目录(如 altas)都指向特定用户的主目录。对于该用户,~ 就指向那个主目录。我可以使用波浪号字符来访问主目录下的任何内容,例如 ~/f.txt 或 ~/altas/f.txt。
在使用相对路径时需要注意:您需要检查您所在的目录,或者当您在运行的程序中使用相对路径时,需要知道该程序将在哪个目录中运行,否则会出现与路径相关的错误。一个用于检查目录的好命令是 pwd,它代表“打印工作目录”。
让我们启动一个Unix shell并回顾我们所学的内容。
这是我的Unix shell,如您所见,我的用户名是 altas。我在 /users/altas 目录下,这恰好是我的主目录。当我在这个目录下输入 ls 时,我会看到我有一个名为 python_for_data_science 的目录。ls 是一个Unix命令,代表“列表”,它列出任何Unix目录中的文件和文件夹。
我想将当前工作目录切换到 python_for_data_science,所以我会输入 cd python_for_data_science,这是另一个Unix命令。现在您会看到,我所在的目录从 /users/altas 变为了 /users/altas/python_for_data_science。如果我在这个目录下输入 ls 来列出文件和文件夹,我会看到两个文本文件:一个叫 fruits.txt,另一个叫 shakespeare.txt。
让我们清屏以便看得更清楚,我们使用另一个Unix命令 clear。现在,假设我在此工作目录中,想进行探索,但我忘记了这个工作目录是什么。在我的情况下,您看得很清楚,因为我配置了命令行来显示我们所在的工作目录。但我们可以使用另一个Unix命令 pwd(打印工作目录)来查看我们当前在哪个工作目录下。
这个目录中有两个文本文件。一个用于显示这些文本文件内容的Unix命令是 cat。如果我输入 cat fruits.txt,我将看到 fruits.txt 的内容。我也可以对 shakespeare.txt 做同样的事情,这是一个更大的文件,它有大约12,000行,但您会看到仍然可以用 cat 显示其内容。让我们清屏。


那么,我如何了解如何使用这些Unix命令呢?有一个Unix实用程序或命令(我们交替使用“实用程序”和“命令”这两个词,它们通常被称为实用程序命令),我可以使用 man 命令(代表“手册页”)来查找任何这些命令的文档。例如,我可以输入 man ls 或 man cat。另外,ls 实际上提供了一组开关选项。例如,我可以使用 ls -l 以长格式显示此目录中的所有内容。在长格式下,我会看到两个文件的信息:谁在什么时间创建了它们、文件大小以及文件的访问权限。我也可以输入 ls -a,这将显示此目录中的隐藏文件和文件夹。因此,除了那两个文本文件,我还看到了 .(指当前工作目录)和 ..(指上级目录)。您知道,python_for_data_science 的上级目录是 altas,这恰好是我的主目录。
如何访问主目录下的任何内容?我绝对可以说 ls /users/altas。我可以说 ls ~,这是我的主目录。或者,请注意我现在如何使用 .. 选项:ls ..,这是我们的父目录。我看到在这个主目录下还有其他文件和文件夹,如果我想访问其中任何一个,我可以说 ls ../temp 或 ls ../cs1501 等等。
让我们清屏并继续下一个视频。
现在我们对shell有了一些基本了解,让我们开始探索Unix系统中的一些命令。在本视频结束时,您将能够:在Unix中列出文件和目录、更改到目录、创建新目录、解释什么是标准输入和标准输出,并说出六个简单的Unix命令。
在任何操作系统中,最基本、最基础的命令通常与能够管理文件系统有关。我们现在将讨论 ls 命令(用于列出Unix系统中的文件)、mkdir 命令(“创建目录”的缩写)以及 cd 命令(用于更改到目录)。我们还将讨论星号 * 字符,它充当通配符,用于替换给定模式中的任何其他字符。例如,如果我列出 *.txt,它将列出所有扩展名为 .txt 的文件。
在切换到shell之前,我还想谈谈shell启动时创建的三个文件描述符。这些描述符始终可供命令使用。
- 标准输入:是用户输入数据的默认来源。它通常是用户的键盘,但可以通过文件重定向轻松切换到另一个文件。
- 标准输出:是程序输出或打印其功能或主要输出的地方。默认是用户的终端或您的shell,但可以通过文件重定向轻松切换到另一个进程或另一个文件。标准输出应保留用于命令或程序的正确输出。
- 标准错误:是用于错误消息或从程序中产生的任何其他非功能性输出的输出通道,例如您的跟踪打印语句。它的默认值也是用户的终端窗口。
如果您是程序员,这三个文件也是程序员在读取、写入或报告错误或程序诊断时使用的。
我们回到Unix shell。我们使用 pwd 查看打印工作目录,使用 ls 查看此目录中的文件和文件夹列表。我们讨论了 cd 来更改到另一个目录。但为了能够更改到另一个目录,我们需要有另一个目录。目前在这个目录中,我们只有两个文本文件。我怎么知道呢?如果我以长格式列出命令,我需要在一行的开头看到 d(如果是目录的话)。所以,在 python_for_data_science 中还没有目录,让我们创建一个。创建目录的命令是 mkdir,代表“创建目录”。所以是 mkdir my_first_dir。现在如果我输入 ls,我会看到 my_first_dir 在那里。我可以现在以长格式显示此目录的内容,我会看到第一行的开头有一个 d。现在,让我们也看看那些隐藏文件。如果您记得我们上一个视频,查看隐藏文件和文件夹的选项是 -a。我们将把它与长选项结合起来,所以我们会说 ls -la(长格式且显示隐藏项)。然后我们查看它。我们看到 my_first_dir 是一个目录,..(代表父目录)是一个目录,.(代表当前目录)也是一个目录。
现在我可以移动到这些目录中的任何一个,我将使用 cd 命令进入 my_first_dir。如果您查看里面,什么都没有。让我们清屏。我们在 my_first_dir 中,如果我们执行长选项 ls -l,什么都没有。那么如何获取那些隐藏文件呢?ls -a。现在如果我们说 ls ..(父目录),我们会看到 python_for_data_science 中的所有内容。我想查看该目录中 fruits.txt 的内容,所以我们会说 cat ../fruits.txt,我们将看到 fruits.txt 的内容。
如果我想将 fruits.txt 复制到我当前所在的目录,我可以简单地使用另一个Unix命令 cp(代表“复制”)。cp ../fruits.txt .。所以我说:获取父目录中的 fruits.txt(../fruits.txt)并将其带到这里(.)。cp 的语法是:复制源文件和目标位置。现在,如果我在这个目录下执行 ls,我会看到这里有一个 fruits.txt 的副本。如果我列出父目录,我仍然有一个 fruits.txt 的副本,因为我只是复制了它,并没有移动它。如果我希望系统中只有一个 fruits.txt 副本在这个目录里,我应该使用移动命令来处理 shakespeare.txt。我们知道父目录有 shakespeare.txt。我可以说 mv ../shakespeare.txt .。让我们清屏。所以我们使用了移动选项,对吧?现在当我说 ls -l 时,我会看到这里有 shakespeare.txt 和 fruits.txt。试想一下,如果我列出父目录 ls ..,您期望在那里看到什么?特别是,您期望在那里看到 shakespeare.txt 吗?答案是否定的,因为我们没有复制 shakespeare.txt,而是使用 mv 命令实际移动了它。
在这个目录中,我们有两个文本文件。如果我只想显示所有文本文件,我可以利用那个通配符选项,记住星号字符,然后说 ls *.txt。所以我在这里向shell传达的是:我想列出任何具有 .txt 扩展名的文件。因此,ls *.txt 将给我此目录中的所有文本文件。
现在我们已经回顾了基本命令,可以进入下一个视频了。
在上一个视频中,我们以讨论标准输入和输出结束,并提到我们确实可以将它们重定向到其他文件。在本视频中,我们将学习如何做到这一点。在本视频结束时,您将能够:说出所有三个Unix文件描述符的名称、使用重定向操作符,并解释 cat 和 more 命令之间的区别。
在Unix和类Unix操作系统上,从命令行启动的每个进程都有三个与之关联的文件描述符。标准输入通常连接到键盘,标准输出和标准错误通常连接到启动应用程序的终端屏幕。然而,这些文件描述符可以被重定向并连接到文件、其他进程的输入/输出等。
- 重定向标准输入:要重定向标准输入使其来自文本文件而不是用户的键盘,我们使用小于号
<操作符。语法是:命令 < 文件名。 - 重定向标准输出:我们使用大于号
>操作符,或者可以显式使用文件描述符1>。文件描述符1(FD1)是可选的,因为>默认重定向到标准输出。语法是:命令 > 文件名或命令 1> 文件名。 - 重定向标准错误:我们需要将文件描述符2与大于号操作符一起使用,写作
2>。语法是:命令 2> 文件名。
我们也可以同时重定向标准输出和标准错误,例如:命令 > 文件5 2> 文件4。这意味着我们将标准错误重定向到文件4,将标准输出重定向到文件5。最后,我们看到所有三个文件描述符被重定向到不同的文件。命令的输出使用 > 操作符重定向到一个文件(用绿色表示)。来自命令的错误流使用 2> 操作符重定向到另一个文件(用蓝色表示)。并且命令从另一个文件(用深红色表示)接受数据,而不是来自标准输入,使用 < 操作符。
关于这些重定向的一个有趣之处是,它们不需要按特定顺序排列,只要您将重定向操作符与文件名保持在一起即可。这意味着所有带操作符的颜色部分应该在一起。事实上,甚至命令名可以放在最后。另请注意,如果您编写了一个期望从终端输入的程序,并且您想从文件提供输入,输入重定向会很有用。
在标准输出和标准错误重定向中,将大于号操作符加倍(>> 和 2>>)将追加到文件,而不是覆盖文件。所以在这个例子中,我们看到命令使用 >> 追加到文件1,命令使用 2>> 将错误追加到文件3。这里还有第三个命令,它展示了一种将标准输出和错误重定向到同一文件的方法。2>&1 意味着将文件描述符2的输出发送到与文件描述符1输出相同的地方,即名为“文件”的文件。
让我们在命令行上回顾其中一些概念。
我们回到Unix shell。如果您记得我们的第一个Unix shell,我们最终在 python_for_data_science/my_first_dir 文件夹中。我可以通过输入 pwd 看到这一点。我也可以在这个目录中执行 ls 并查看其内容,您可能记得我们有两个文件:fruits.txt 和 shakespeare.txt 被移动到了这个目录。
现在我将使用 cat 命令显示 fruits.txt 的内容,我看到大约有8到9行。如果我对 shakespeare.txt 做同样的事情,您会注意到一些有趣的事情:它无法完全显示在shell中,所以它会快速滚动文件中的所有12,000多行,然后带我回到文件的末尾,我只能看到文件的结尾。如何解决这个问题?Unix中有另一个命令叫 more。您可能记得 man 命令是Unix中另一个用于查看所有这些命令手册页的命令。所以如果我说 man more,我会看到 more 命令的描述;如果我说 man cat,我会看到 cat 命令的描述。如果我说 more shakespeare.txt,它会从第一行开始显示,显示尽可能多能适应我的Unix shell窗口的内容。我只需按键盘上的空格键就可以浏览这个文件并查看所有行,或者我可以按 Q 键退出。所以这里有一个区别:cat shakespeare.txt 会滚动整个文件,我们看不到文件的开头(当然我们可以用鼠标向上滚动,但我们不希望这样)。而 more shakespeare.txt 会显示尽可能多能适应我们shell窗口的文件内容。
为什么这有用?因为我们想浏览文件,有时对于这些较长的文件,我们想对其做些操作,比如排序或计算行数。假设我想对 fruits.txt 的内容进行排序。sort 是一个Unix命令,我们可以用它进行快速数据操作。我会说 sort fruits.txt,我将看到 fruits.txt 的所有内容按字母顺序排序。现在我将获取排序后的 fruits.txt 并将其保存到另一个文件中。这里要使用的是标准输出重定向大于号 > 符号,我会称该文件为 fruits_sorted.txt。我在标准输出中看不到任何内容,因为我将标准输出重定向到了这个文件。如果我现在 cat fruits_sorted.txt,当然,我们可以像之前看到的那样直接将文件名提供给 cat 命令。或者,cat 实际上可以接受来自文件的重定向输入,例如 cat < fruits_sorted.txt,我会看到该文件的内容。所以,这与我们之前看到的命令的标准输出相同。
接下来我要做的是进一步操作这个文件,只获取其中的唯一行。我可以查看 fruits_sorted.txt 并获取其中的唯一行。这里我们看到重复的苹果和桃子,现在只剩下苹果、葡萄、甜瓜、橙子、桃子和草莓,共六行。所以这个文件中有六种唯一的水果名称。如果我想将其保存到另一个文件,我会说 uniq fruits_sorted.txt 并将输出重定向到 fruits_unique.txt。我可以快速查看 fruits_unique.txt 的内容,看到它与命令的标准输出相同。如果我现在列出目录,我会看到四个文件,而不仅仅是 fruits.txt 和 shakespeare.txt,我还会看到 fruits_sorted.txt 和 fruits_unique.txt。
为什么这有用?因为有很多Unix命令,我想快速将输出保存到文件中,也许在这个例子中计算数量。我如何计算唯一水果的数量?那就是计算 fruits_unique.txt 中的行数。如果您记得我们之前的视频,wc 命令有一个 -l 选项可以只给我们文件中的行数。所以我会说 wc -l fruits_unique.txt,它会给我数字6作为行数。所以在我们原始的 fruits.txt 中有六个唯一的水果名称。您可以看到,我们已经开始使用我们拥有的Unix命令进行一些数据探索。我们有一个包含一堆水果名称的文件。我们想看看其中有多少个唯一的水果名称,或者我们可以查看其排序选项。所以我们在用这些数据做些什么。如果您将该文件视为数据集,我们正在操作和探索该数据。
我也可以使用这个命令,比如在Unix中使用 who 命令,它显示当时有多少用户或谁登录了Unix系统。如果我只说 who,我会看到三个 altas 登录,因为我打开了三个shell。我可以简单地将此命令的输出重定向到一个名为 names.txt 的文件。我会看到所有这些名称都保存在我的shell中的 names.txt 文件中。我如何使用它?我可能会浏览它,并向当时登录的所有用户发送电子邮件。有许多Unix命令利用标准输入。之后,也许我会说,我将这些名称作为标准输入提供给另一个命令,获取这些名称,也许使用 mail 命令向这些用户发送标准消息等等。实际上,我可以通过将正确的输出重定向到正确的文件来操作这些文件,并可以将其用作在另一个操作中生成的数据集。
但重定向是如何工作的?我想向您展示的另一件事是:命令组一起执行,以及重定向如何应用于与其耦合的命令。要将两个命令串在一起,我可以说 pwd,然后紧接着执行 ls -l,如果我使用分号 ; 操作符。在 pwd 之后输入分号,意味着执行 pwd,然后紧接着执行 ls -l,就像在shell上一个接一个地键入这两个命令一样。所以我将在这里看到两个输出:pwd 的输出,然后是我刚刚键入的 ls -l 操作的输出。
现在假设我想将这两个命令的输出重定向到一个名为 output.txt 的文件。请思考一下,您期望在标准输出中看到什么?您实际会看到的是第一个命令的输出,因为这个重定向操作符 > output.txt 只应用于与其捆绑的命令。如果您希望所有这些输出(pwd 和 ls -l 的输出)都进入 output.txt,我们需要将它们放在括号中。所以我可以输入 (pwd; ls -l) > output.txt。现在如果我 cat output.txt,我会看到这两个命令的所有输出都被重定向到了 output.txt。因此,我们实际上通过将这两个命令缝合在一起并将它们放在括号中,创建了一个复合命令。
我想向您展示的另一个不太有趣但有用的Unix结构是:我们可以通过将重定向输出发送到空设备 /dev/null 来抑制这些重定向的输出。在这个例子中,我本可以说,将输出发送到 /dev/null,而不是 output.txt。这样它就不会去任何地方,只会被忽略;它不会显示在标准输出上,也不会进入特定的文本文件。所以有一个Unix程序的笑话:如果你想忽略某人,你只需告诉他们“我把你重定向到 /dev/null 了”,这就是这个说法的来源。
我们刚刚回顾了标准输入/输出和重定向,希望您能自己更多地练习这些重定向。在下一个视频中,我们将回顾其他一些有趣的Unix功能。
正如我们在现场演示中看到的,当我们将命令输出重定向到文件时,为了将数据从一个命令传输到另一个命令,会保存很多文件。相反,我们实际上可以使用Unix中的其他东西将一个命令的输出管道传输到另一个命令。在本视频中,我们将解释什么是Unix管道,并回顾一些可以转换通过管道传输给它们的内容的特殊命令,这些命令称为过滤器。在本视频结束时,您将能够:用一个例子描述什么是Unix管道、说出五个过滤器命令的名称,并区分重定向输出和使用Unix管道。
简单来说,Unix管道是一种将一个命令的输出发送到另一个命令的输入的方法。在这个例子中,Unix命令 cat 的输出被管道传输到另一个Unix命令 wc,后者计算输出中的行数、单词数和字符数。请注意这个图示:当一个命令将其输出发送到管道时,该输出的接收端是另一个命令,而不是文件。在大多数情况下,接收命令将被触发运行。然而,在一些特殊情况下,命令会对通过管道接收的输入进行处理,就像这里的 wc 计算提供给 cat 命令的文件内容一样。我们称此类命令为过滤器。过滤器是一个程序,它接受输入并以某种方式转换其输入。当过滤器放在Unix管道之后时,它接受管道另一侧命令的输出并将其用作输入。grep、more、less、sort 和 uniq(正如我们刚刚看到的)都属于此类命令。
grep:将在给定的输入流中搜索包含给定字符串的行或查找模式。more:正如您所见,将显示尽可能多能适应您的shell窗口的内容(类似于less)。sort:将按字母或数字顺序对输入流中的行进行排序。uniq:将给出输入流中的唯一行。
但主要的一点是,所有这些命令都将以某种方式转换它们的输入。
让我们现在回顾一些这些管道和过滤器,以了解它们的作用。
ls -la | more:有时当我们查看ls的输出时,该目录中有太多文件和目录,无法适应您的shell。因此,如果您简单地将其管道传输到more,您将看到ls -la的部分输出,并且通过按空格键(正如我们在上一个视频中所做的那样),您可以看到该目录中其余的行或文件和目录。cat 文件名 | wc:我们将看到cat输出中的行数、单词数、字符数,即文件名内容中的行数、单词数、字符数。man cat | grep file:grep在这里做的是,它将在cat命令手册页的每一行中查找“file”的出现。因此,您将获得man cat的输出,并且只能看到该手册页中包含“file”文本的行。ls | grep txt | wc -l:我们有ls的输出,我们正在该输出中查找“txt”。grep将输出包含“txt”的每一行,但我们并不止于此,然后该输出被管道传输到wc。wc输出中的行数将告诉您该目录中有多少个文本文件(如果您试图计算那里的文本文件数量的话)。who | sort > current_users.txt:这个例子很好地结合了重定向和管道。这里我们看到who被管道传输到sort,who将告诉您当时谁登录了您的Unix系统。sort命令将按字母顺序排序,并将其写入名为current_users.txt的文件中。
一个有趣的事情是,您可以像我们在第四行所做的那样,将多个管道链接在一起,创建一个小脚本。所以这里的双管道用于此目的。
在下一个视频中,我们将执行其中一些管道和过滤器,并使用这些命令探索一些数据集。
我们又回到了我的shell,我们打开了终端。让我们通过回忆上次所做的事情来开始我们的管道和过滤器现场编码会话。记得我们有一个文件 fruits.txt。在这个目录中,我们想计算其中唯一水果名称的数量。我们说过 uniq fruits_sorted.txt,并将其管道传输到 fruits_unique.txt。就像这样,所以我们将 uniq 命令输出中的唯一水果名称重定向到一个名为 fruits_unique.txt 的文本文件。然后我们使用这个文本文件与 wc 命令(单词计数命令)和 -l 选项来计算该文件中的行数。为什么?因为行数将给我们水果的数量,因为每一行都是一个唯一的水果名称。如果我实际显示该文件的内容,我们看到每一行都是一个唯一的水果名称。我可以说 wc -l fruits_unique.txt,我们可以看到6是其中唯一水果名称的数量。
您可能已经注意到这个命令的问题是:我们必须将 uniq 命令的结果保存到一个额外的文件中,只是为了计算水果数量。我们能做得更好吗?答案是肯定的,如果我们利用我们刚刚讨论的管道和过滤器。我们本可以只使用 uniq fruits_sorted.txt 并将其管道传输到 wc -l。让我们试试:uniq fruits_sorted.txt | wc -l,我们得到了6,作为其中的水果数量,非常完美。实际上,我们本可以做得更好。我们甚至不需要 fruits_sorted.txt 文件。我们可以直接输入,记得原始的 fruits.txt 文件吗?cat fruits.txt,它里面有所有这些水果名称。我们本可以直接说 sort fruits.txt | uniq。uniq 将接受 sort fruits.txt 的输出,但该输出将是排序后的名称,uniq 将从中挑选出唯一的条目。所以我们可以将其管道传输到 uniq,它将给我们唯一水果的数量。然后我们可以将其管道传输到 wc -l。注意我们得到了相同的答案,相同的结果,而且更快、更高效。您是否注意到,与我为获得相同结果而键入的所有其他命令相比,这一个命令有多短?这就是使Unix如此强大的功能之一,正如我们在第一个Unix视频中描述的那样。
现在让我们使用 man 和 grep 命令再看一个过滤器命令。正如我提到的,man 是Unix中的手册页命令。如果我说 man cat,我将获得 cat 命令的手册页。如果您查看此手册页,您会注意到“file”是多次使用的单词之一,但具体有多少次?我们在这个例子中试图计算它。我实际上可以说 man cat | grep file,使用 grep 在其中查找“file”这个词的模式。所以我可以将其管道传输到 grep,并找到该手册页中包含“file”一词的每一行。这里我们看到,在该手册页中有相当多的行提到了“file”这个词。现在我可以将其管道传输到 wc 命令(单词计数命令)并仅使用行数选项,我会看到该手册页中有10行提到了“file”这个词。
让我们继续,清屏。最后,让我们看一个管道和过滤器,它显示我的Unix系统中正在运行的进程。我们以前没有做过的一个命令是 ps,这是一个用于报告进程状态的命令,在Unix中代表“进程状态”。我们将使用 ps 来显示与所有终端关联的所有进程。所以要做到这一点,我实际上使用一些 ps 选项。如果我只说 ps,我会看到这里只有几个命令在运行。但如果我说 ps -aef,我会得到与所有终端关联的每一个进程,以及在我的Unix系统中运行的每一个进程。实际上,我们看到输出相当多,有很多进程,甚至无法适应我的终端窗口。现在让我们添加 more 命令,以便更好地查看这里的情况。为此,我实际上要稍微减小我的字体大小,以便向您展示更多内容。让我们执行 ps -aef | more。现在我将看到所有这些进程,正如您在顶部看到的。有UID、PID、PPID,这些是与进程关联的不同标识符。其中一些是父进程,一些是Unix系统中当前运行进程的唯一ID,一些实际上用于启动我们的终端和我们的一些Unix核心功能,这些属于系统和我们的内核。但如果您浏览这个,您会注意到第一行的UID并没有真正排序,我们可能想使用我们的 sort 命令对其进行排序。让我们创建一个管道来对 ps 命令的结果进行排序,但这次我们将添加一个重定向来保存排序过滤器命令的结果。如果我只是说 ps -aef | sort,也许我会将其管道传输到 more,这样我现在可以看到它们都排序了。您在这里看到,包括第一个标题行,它仍然在我的终端中。也许我想稍后处理该文件以了解其中一些命令,所以我可以不使用 more,而是直接说将其重定向到 output.txt。现在,output.txt 将包含格式化的数据,我想使用它。我能够以一种方式快速准备数据以供进一步分析。
我想在这里提到的一件事是:我们有管道传输到 sort 命令,正如您所见,并且我们有过滤器(即 sort 命令),它处理并对 ps 命令的输出进行操作。所以 ps 命令被管道传输到一个过滤器(这恰好是 sort 命令),但有一个重定向——记住,有五种命令,其中一些是过滤器命令,一些只是不对此输入执行任何操作的常规命令,我们重定向到文件,比如 output.txt 是一个文件,我们将 sort 的输出(通常进入标准输出)重定向到一个名为 output.txt 的文件。
在这个对管道和过滤器的快速介绍之后,在下一个视频中,让我们专注于一些过滤器命令和其他我们可以利用的Unix实用程序,以进行快速的探索性数据分析,就像我们在这里开始的那样,通过转换 ps 的结果并将其保存到文件 output.txt 中(这些结果的排序版本)以供进一步数据分析。
在我们的管道和过滤器演示中,我们看到了Unix命令在链接在一起时如何促进复杂的数据操作。过滤器命令通常为数据分析师提供了一种快速检查和转换数据的方法。现在我们将回顾其中一些命令并提供更多示例。在本视频结束时,您将能够:使用Unix命令对文本数据进行排序、清理、剪切和探索、在Unix shell上绘制数据,并使用管道和过滤器在Unix中进行快速数据探索。
当我们处理文本文件或Unix命令的输出时,我们主要进行文本数据操作和搜索。一些有助于实现这些目标的有用命令是:grep、cat、wc(单词计数)、sort 和 uniq,以及 head、tail、cut、sed 和 find。虽然我们在早期的编码会话中讨论了前五个命令,但我们还没有看到这里列出的第二组命令。
head命令用于列出文本文件或输入流的前N行。tail命令列出最后N行。cut命令是一个非常强大的命令,它为我们提供了一种剪切文件每一行的一部分的方法。sed或流编辑器用于对输入流(如文件或来自管道的输入)执行基本的文本转换。find将使我们能够在文件系统或层次结构中进行快速搜索。
这些命令最好通过示例来学习,所以让我们看看可以使用这些命令在Unix中解决的两个问题。考虑到我们拥有的莎士比亚作品的文本文件,我可以尝试找出他所有作品中使用频率最高的单词,甚至可以创建前15个单词的图表。我也可以专注于我的基于Unix的系统,并查找在我的系统中运行最多进程的前三个用户ID。我还可以只转换其中一个文件,比如我们有的 fruits.txt,使其所有字母都变为大写。我们将在即将到来的现场编码会话中关注这三个问题,但让我描述一下第一个问题的管道和过滤器语句。
在第一个命令中,我们将标准输入重定向为来自 shakespeare.txt。由于此文件每行有许多单词,我们首先关注 sed 命令,将单词之间的每个空格转换为换行符。在此命令之后,我们应该有一个每行一个单词的标准输出流,以及一些在运行此命令之前存在的空行。然后我们对此输出进行排序,并删除剩余的空行。最后这两个用于排序和删除剩余空行的命令的顺序无关紧要,因为它们为即将到来的 uniq 命令准备输出流。现在我们需要找出文件中唯一单词的数量。uniq 命令的 -c 选项为我们提供计数以及各个单词(计数所属的单词)。现在我们有了每个单词的计数,我们需要使用 sort 命令的 -nr 选项进行数字排序。最后,我们使用 head 命令获取 sort 命令输出中的前15行,并将输出写入一个名为 counts_vs_words 的文件。这个长而有效的单行命令帮助我们在shell上进行快速数据探索,而无需编写大型程序。想想看,否则您将需要使用多少行Java或C代码来执行相同的操作。请注意,在除macOS之外的Unix系统中,sed 正则表达式可能看起来更简单,它只是说:将每个空格字符(s)替换为换行符( )。
接下来,我们看到这些命令的类似用法用于我们的第二个问题,我们将在现场编码会话中更多地关注这个问题。最后,我们将使用一个新命令 tr 命令将 fruits.txt 的内容转换为全部大写字母。接下来,我们将可视化我们的发现以完成我们的探索性分析。Unix中用于绘图的简单工具称为 gnuplot。虽然在本课程中我们将使用Matplotlib和其他更高级的Python包进行可视化,但我想向您展示一个Gnuplot示例,以演示您可以通过shell使用Gnuplot做什么。
现在我们回顾了所有命令,让我们开始在Unix shell上进行现场编码会话。

我们回到shell。这是我的 python_for_data_science/my_first_dir 工作目录,所以我们可以在这里看到 pwd,我们可以看到我们在哪里。让我们执行 ls,在这个目录中,我们有 fruits.txt,我们有 shakespeare.txt,我们探索过它们。让我们使用 head 和 tail 命令以及一些管道和过滤器进行更多探索。我可以首先查看 fruits.txt 的前五行,如果您记得 fruits.txt,它里面有一些水果名称,对吧?我将使用 head -5 fruits.txt 获取前五行,我看到我得到了前五行。然后也许最好查看我拥有的所有水果文件的前五行。在这种情况下,记住星号是通配符操作符,我们将使用它。所以如果我只是说 head -5 fruits*,我会看到那里所有的水果文件,对吧?fruits_sorted 的前五行(苹果被很好地排序了)、fruits_unique(我们没有任何重复的名称,因为它们都是唯一的名称),然后我们有 fruits.txt。所以我们在上一个会话中做了这个。我也可以将输出管道传输到 cat 命令,但它无论如何都会显示它接收到的输入,但为了完整性,让我们清屏并说 head -5 fruits* | cat,我们看到相同的输出。

让我们使用相同的逻辑查看所有水果文件的最后三行。停一下,想想您将如何使用 tail 命令获取最后三行。我们只需将这里的 head

在本节课中,我们将学习Jupyter笔记本的基础知识,包括其核心功能、创建与运行代码、使用Markdown进行文档编写,以及如何管理笔记本文件。Jupyter笔记本因其结合代码、文档和结果于一体的特性,已成为数据科学社区中广泛使用的工具。
在课程早期,我们已经见过Jupyter笔记本的示例。本周,我们将开始一起构建笔记本,并更深入地使用它们。首先,我们需要讨论为何选择Jupyter笔记本。
通过本视频的学习,你将能够描述Jupyter笔记本的特性,这些特性使其在数据科学社区中迅速获得广泛认可。
Jupyter提供了许多功能,其中一些我们已经提及,但让我们回顾一下。数据科学过程涉及我们在课程早期学到的步骤。Jupyter笔记本允许我们通过结合笔记、代码和图形来记录这个过程。最重要的是,结合这些功能使其他人能够阅读笔记本,理解每个步骤背后的动机以及决策的原因,这促进了良好的协作。

与文档和协作的重要性相一致,这也体现了良好的科学实践。如果其他人想要检查你的结果或在你的发现基础上进行研究,他们可以确切地看到你是如何进行研究的。几个世纪以来,对他人方法的复制和检查一直是科学过程的核心要素,但计算工具的出现引发了关于如何在使用计算进行科学时最好地确保此类实践的讨论,而Jupyter笔记本正是为此而生。

Jupyter笔记本不仅包含你的笔记和过程,还包含你的结果。你可以通过各种方式轻松地与同事分享笔记本,并在过程中展示你的结果。
最后,正如我们之前提到的,Jupyter支持Julia、Python和R这三种当今进行数据科学最流行的编程语言,以及其他语言。我个人一看到Jupyter提供的功能就转向了它,我的许多同事也做出了同样的转变。因此,本课程旨在让你熟悉并开始使用笔记本。
为了让你入门,我们将从Jupyter笔记本的基础知识开始,以便在本视频结束时,你将能够创建Jupyter笔记本并在其中运行Python代码。
现在你已经完成了Jupyter笔记本的设置,并且我们回顾了Jupyter的一些背景知识,让我们开始使用笔记本环境。
你看到的第一个页面称为仪表板,它基本上列出了你启动Jupyter笔记本的文件夹内容。你可以通过这个仪表板在文件系统中导航,就像任何其他文件资源管理器界面一样。需要注意的是,这个文件系统是Jupyter笔记本服务器运行的地方,它甚至可能在另一台机器上。如果你不使用本地主机作为服务器,你只会看到另一个界面。
此时,我想提醒你,如果你还没有开始,请跟着我一起操作。我们知道在线课程成功的最大障碍之一是开始使用基础设施和学习环境。所以我真的希望你和我一起做。我们只是要启动我们的第一个笔记本,运行一些初始代码,并添加一些文档,这不会花费你太多时间,但会让你开始运行。如果你现在设置好,在课程的其余部分,你将能够跟随我们展示的材料在笔记本中操作,并自己尝试代码。
现在,让我们通过“新建”按钮创建一个新笔记本。在右上角,我们将选择Python 3作为我们的Python版本。
现在我有了一个笔记本。我点击顶部的“未命名”来命名它。我将其命名为“Intro notebook”。我们重命名了我们的笔记本。现在,如果我们回到仪表板,我们会看到一个名为“Intro notebook.ipynb”的文件,这就是我们刚刚创建并重命名的笔记本。
一开始,有时会有些混淆,因为笔记本应用程序和笔记本文件都称为“notebook”,但它们是分开的。笔记本应用程序是一个Web应用程序,它在浏览器中创建界面并执行Python和其他语言的代码。笔记本文件是一种文件格式,扩展名为.ipynb,它将代码、图像和文本保存在一个可以共享的单一文档中。正如我们之前所说,这个功能在数据科学中获得了很大的流行。
你可能已经意识到,“Intro notebook”在不同的浏览器标签页中。在你的浏览器中,任何我打开的笔记本都会有一个标签页,你可以同时打开多个。现在,让我们开始使用代码单元格进行编码。希望你正在跟着操作,但如果你落后了,请随时暂停视频并尝试我展示的每一步。一开始花些时间是正常的,所以如果有时感到困惑,请不要担心。
我现在点击第一个单元格。只需用鼠标点击笔记本中的矩形单元格,然后输入Python代码,比如我可以输入print("Hello world"),这是任何计算机科学编程课程中的传统问候语。
我可以通过几种不同的方式运行这段代码:一种是转到顶部的工具栏并按下运行按钮,我应该会看到结果,就像我们在这里看到的“Hello world”被打印出来;或者我可以重新点击那个单元格,同时按下Shift + Enter,它应该会运行相同的单元格。
请注意,这是在称为内核的Python进程中执行代码。基本上,每个打开的笔记本或浏览器标签页都有一个在后台运行的内核。Jupyter笔记本应用程序与内核通信,让它加载数据并执行代码。Shift + Enter或那个播放按钮会在执行时或内核执行时在第一个单元格下方创建另一个单元格。所以这里的这个是我们的第二个单元格。
因此,我们可以说Jupyter笔记本是单元格的集合,其中一些包含我们刚刚做的代码。现在我们已经学会了使用单元格,让我们执行更多代码,例如,你可以将笔记本用作计算器。我们可以问的一个问题是:一年有多少秒?所以我们需要计算天数乘以24(小时)乘以60(分钟)乘以60(秒)。记住,我们可以通过工具栏上的运行按钮或Shift + Enter来运行它。
所以我通过按Shift + Enter来运行它。我得到了那个大数字作为输出。你可能已经注意到,当我打印某些东西时,它会显示为代码单元格下方的文本。如果我像这里一样运行一个没有赋值或打印语句的计算,它会作为输出行出现,我们为那个数字有一个输出行。
那个数字很难阅读,它是一个很大的数字,所以让我们把它转换成百万。为此,在下一个单元格中,我将输入_ / 1e6,然后按Shift + Enter。我们看到输出变成了以百万为单位的数字。这里让我解释一下我做了什么:_指的是最后一个执行的单元格的输出,因为我们之前执行了那个输出行。我们使用10的6次方的简写科学记数法将其转换为以百万为单位的数字,然后我们运行它,大约是3153.6万秒,这样更容易阅读。
现在我在想,闰年呢?因为闰年有366天。Jupyter笔记本的好处是你可以回到单元格,更新它并再次运行。所以,为了计算闰年,我只需转到那个输出行,它显示了执行的顺序,我将去更新那行,将365改为366。修改单元格后,我们将用新问题计算秒数:闰年有多少秒?所以我按Shift + Enter,我们看到数字已经改变。
但由于我们还没有执行下一个单元格,那个数字还没有改变。但环境将我们放到了下一个单元格,所以我们可以再次重新运行那个相同的单元格。所以我们有那个输出行,并且我们知道那个输出行是在之后执行的,因为那里的数字顺序更大。所以我现在要在那个输出行上按Shift + Enter,现在是输出行,我们知道那个数字已经更新了。
很好。但也许我们希望两者都在我们的笔记本中,我想有闰年有多少天的问题,也想要有平年。那我们该怎么做?这里我们可以复制并粘贴代码单元格,为与两个问题相关的代码设置单独的单元格,而不是完全更新单元格。为此,我将使用工具栏上的剪切和粘贴按钮。
所以我要转到那个单元格,点击那个输出行,与之关联的单元格,这里有剪切和粘贴,我只需复制,然后按粘贴按钮。现在我有两个单元格。在第二个单元格中,我可以输入365 * 24 * 60 * 60,然后重新运行它。我将得到两个问题的答案:平年和闰年各有多少秒?当然,我也需要对百万转换做同样的事情,所以我可以去那里粘贴。我需要点击我想要粘贴的上方单元格。
所以如果我现在去输出行并按Shift + Enter运行那个,按Shift + Enter运行下一个,按Shift + Enter运行输出行,现在它变成了输出行,按Shift + Enter运行最后一行,我们一切都按顺序很好地执行了。
到目前为止,我们使用了只有一行的代码单元格。但实际上,代码单元格中的代码不必像我们在快速示例中所做的那样只有一行,你可以在笔记本中执行任何类型的Python代码,有时可能简单到导入一个模块,有时可能是打印一个变量或创建复杂的分析或图表。
但现在让我们用一个两行代码的例子。如果你在那里运行任何Python代码,我现在点击最后一个单元格。如果我简单地说x = 4 + 3,然后在下一行print(x)。现在当我按Shift + Enter运行时,环境将运行那个代码块。
当你点击这个时,你会注意到一些事情:如果我使用上下箭头,我可以在不同的代码行之间移动。一旦我到达顶行,我可以转到单元格。但是当我有很多行并且想要快速在不同代码单元格之间切换时,这可能有点挑战。也许你想在不使用鼠标的情况下,仅通过键盘快速上下移动几个单元格。
为此,笔记本界面被实现为模态的,这意味着如果你在单元格边框处按Escape键,就像我在这里要做的那样,在输出行单元格处按Escape,你现在可以使用键盘上的上下箭头来更改单元格。在我这样做之前,光标在print(x)行,所以如果我不在那种我的单元格被突出显示为蓝色的转义模式,如果我按上箭头,我会进入x = 4 + 3行,现在我可以按上箭头在单元格之间移动。这是一个非常有用的功能,我可以去执行这些,并且它仍然处于那种模态模式。
那么现在我们如何摆脱其中一些单元格呢?让我们点击底部的代码单元格,它变成了绿色。让我们输入。我会输入print("temp"),因为我剪切了这个。现在,也许我想快速检查一些东西,但我不希望它永久留在我的笔记本中,我可以直接点击它,然后在上面的工具栏中简单地选择“剪切”,它就会被删除。
让我们在这里结束代码单元格的介绍,随着课程的进行,你会看到更多。接下来,我们将实际看看笔记本中一些称为Markdown单元格的特殊单元格。我们刚刚学会了在笔记本中运行代码,但还没有使用记录我们分析的功能。在这个视频中,我们将学习如何做到这一点。
通过本视频的学习,你将能够在Jupyter笔记本中添加和格式化文本。
现在我们将从上一个视频结束的地方继续,使用另一种称为Markdown单元格的单元格类型。
让我们打开一个新单元格,这次我们将使用加号工具栏按钮。我按加号,然后会看到一个新的单元格**入到突出显示的单元格下方。我们现在可以点击单元格,并通过在笔记本右上角的下拉菜单中选择“Markdown”来将单元格的用途从代码更改为Markdown。
所以现在这个单元格,当我点击它时,它是一个Markdown单元格。这意味着我们可以在其中编写文本而不是代码。这对于记录你的数据分析流程非常有用,所以我可以直接输入“Hi”,然后运行它,我会看到有文本,所以我实际上可以再次点击那个文本单元格,回到Markdown单元格模式。
尽管Markdown单元格是一种设计简单的文本标记,类似于文字编辑器,但你可以使用这些Markdown单元格进行相当多的格式化和美化,以及科学编辑。Markdown单元格支持HTML和其他文本格式化语言,如LaTeX。
例如,我们可以创建项目符号列表。让我在这里实际做得更好一点。然后我转到下一行,输入* (星号加空格),我放在这个空格后面的所有内容都会被渲染成一个项目符号列表,所以我可以输入“One fish”、“Two fish”、“Red fish”、“Blue fish”。当我运行这个并按Shift + Enter时,它会将我刚刚输入的内容创建成一个项目符号列表。所以我们现在不是运行代码,而是通过这个文本标记单元格渲染HTML。
你可以用它做很多事情,我们也可以创建标题或标题。为此,我们将使用井号#符号。所以如果我们输入一个井号,它已经显示这是标题一。这是Markdown。如果我们输入两个井号,它会使其变小一点,就像标题二。如果我们继续这样,三个井号将是标题三,依此类推。当我按Shift + Enter时,我会看到这些被渲染为我想要的,即标题、项目符号列表和文本。
我们还可以做粗体文本。所以我只需输入示例文本,如果我按Shift + Enter,它会将其格式化为粗体。我可以通过使用斜体字体来使事物变为斜体或强调事物,我可以输入*示例文本*,它已经开始将其编辑为斜体。或者斜体。我看到它强调了最后一行。
链接,比如google.com,我们怎么做?如果你直接放一个http://google.com在这里,它会自动将其渲染为指向google.com的链接。
当然,有很多渲染文本的方式,如果你想了解更多,我们在后续阅读中提供了一个Markdown指南。另外,作为旁注,大多数人认为Markdown是代码注释或记录代码,但我认为它更像是写科学论文或白皮书,你可以用Markdown创建非常复杂的编辑,例如,你可以使用LaTeX方程创建方程。LaTeX是一种文档编辑语言,具有用于科学写作的许多文本组件的语法,包括编写方程。
所以让我们转到下一个单元格。也许在这里我可以向你展示,我将再次从那个代码单元格切换到Markdown单元格。然后我在那里输入一个LaTeX方程。要在LaTeX中写方程,我需要将它们写在两个美元符号之间,就像这样。然后我就可以写我的LaTeX方程,然后我们会看到它如何被渲染成一个格式漂亮的方程。让我们渲染这一行。正如你在单元格中看到的,我们输入了一个漂亮的小LaTeX方程代码。即使它是一个Markdown单元格,而不是代码单元格,它也知道如何将其渲染成这个美丽的方程。
所以在这里我可以添加更多文本,这是粗体的LaTeX方程。现在我输入它,当我按Shift + Enter时,我会看到它被很好地格式化,几乎就像你在科学论文中看到的那样。这非常棒,例如,当你从科学论文或你想尝试的数据科学算法中实现代码时,其中涉及很多数学,你当然可以先用LaTeX写一个优雅的方程,对人类来说更容易阅读,然后你可以在紧随其后的代码单元格中的Python函数中实现它,这使你的合作者或读者更容易理解。
接下来,我们将讨论如何在笔记本中使用代码单元格进行成像。
你现在已经处于使用笔记本的良好状态,但让我介绍一些更有用的实践。
通过本视频的学习,你将能够管理Jupyter笔记本文件并与你的团队分享笔记本。
所以在这个视频中,我们将更多地讨论如何更好地格式化和注释我们的笔记本,并在图像周围使用一些图像和Markdown。
我们将在本课程的第5周学习关于绘图和Python的更多内容,但如果你能耐心听我讲,并且请不要担心如果你不理解代码,我想向你展示如何在笔记本的代码单元格中生成图像,以及如何使用周围的Markdown来解释那个单元格。
所以我要转到我的笔记本,你看到最后一行,如果你点击它,如果你不确定它是Markdown还是代码,你已经在这里看到它指示是代码,但你也可以通过工具栏中的Markdown下拉菜单来检查。
所以这里我将使用一个简单的matplotlib函数叫做plot,我需要导入一些东西。它基本上是说导入pyplot和plot函数,我们将使用plot函数来绘制一个向量,[0, 1, 0, 1]四个值,不是吗?如果我在这里按Shift + Enter,我会看到图表被生成。再次,我们有四个值[0, 1, 0, 1],我们只是使用一个简单的matplotlib函数绘制出来,我们将在本课程后面看到更多关于如何使用matplotlib创建可视化等内容。
现在也许我想解释这个图表,我该怎么做?我转到点击它下面的代码单元格,然后将其转换为Markdown。我可以简单地写:“这是一个使用matplotlib绘制的向量图。”就这样,我运行这个,这样我就可以看到我的文本实际上就在那个图表下面,并进一步解释那个图表。
让我们稍微向上移动一下我们的笔记本。为此,我将使用Escape键,然后使用上下箭头在单元格之间移动。我想回到我们计算的第一个问题:闰年有多少秒?这只是代码。在它上面,我想使用Markdown添加一行文本来解释那段代码是做什么的,或者我在那里试图做什么。
所以我转到那个单元格上方的单元格,也就是输出行,我转到那个输出行单元格,然后转到我的工具栏并点击“添加”。这里我可以点击我添加的新单元格,将其转换为Markdown,然后输入:“下面是计算闰年秒数的代码。”如果我们运行这个,我们可以看到我们正在很好地记录,我还没有改变太多,但我只是继续运行笔记本,看看如何通过按Shift + Enter逐个运行所有内容,每次都会将我们带到下一行。
现在我们已经完成了我们的笔记本。我们需要保存它。当然,笔记本应用程序会在你输入时保存检查点,但如果你想确保,你也可以选择“保存”或使用那个保存按钮。你会看到当我使用检查点时,这里创建了一个检查点,所以如果我想在以后转到该检查点版本的笔记本,我可以去点击它。
所以现在让我们关闭我们的浏览器标签页,我们已经完成了我们的笔记本,保存了它,现在让我们关闭浏览器标签页并转到我们的仪表板。在我们的仪表板上?我们看到那个绿色的笔记本就在我们的笔记本文件旁边,它显示正在运行,所以尽管我们关闭了它,运行笔记本的内核仍在运行。所以既然它在运行,我们可以直接点击并轻松打开它。
所以现在如果你想确保内核在我们关闭笔记本后没有运行,也许我们不会在近期使用它,并且我们想确保一切都保存并关闭,我们可以转到“文件”,然后在那里选择“关闭并停止”。当我们关闭它后回到仪表板时,我们会看到绿色的笔记本变成了某种灰色,笔记本不再运行了。
当然,要运行它,我们只需要重新打开笔记本。记住,内核被关闭了,但当我查看这个笔记本整体时,我之前所做的一切都保留了下来,所以一旦它运行,它就会一直保留,直到我去重新运行笔记本。所以如果我与协作者分享这个,他们将能够看到我的输出,然后他们可以更改并重新执行,或者他们可以通过按Shift + Enter重新执行相同的操作,他们甚至可以在内核下做“重启并运行全部”之类的操作。所以这个内核有不同的运行选项。
我想讨论的另一件事是,假设你完成了分析,并想把它发送给你的同事,他们当然可以像查看报告一样查看你的分析,但他们也可以在自己的机器上重新运行或轻松修改笔记本。如果你真的想保存一个不可执行的笔记本版本并保存在你的档案中呢?我们可以通过转到这里的文件菜单选项来做到这一点,你可以将笔记本下载为多种不同的格式,这里我将下载为HTML。
所以当我将我们刚刚创建的笔记本下载为HTML文件时,你会看到笔记本看起来就像我们在笔记本应用程序中看到的那样,但它是一个没有顶部名称和顶部工具栏的HTML文件,不可编辑,所以我们有了一个可以存档的笔记本版本,带有你上次运行该笔记本时的输出,这也使得随着时间的推移或如果你的协作者只想查看不可执行的结果,更容易记录每个系统的结果,你可以通过笔记本界面分享HTML文件或PDF。
至此,我们将结束Jupyter的介绍。在课程的其余部分,随着我们学习更复杂的Python数据科学模块,我们将向你展示其他一些笔记本功能。希望你会像我们一样享受使用笔记本,并跟随我们展示的所有不同应用的笔记本。
在对Jupyter笔记本进行了简要概述之后,我们现在将开始进一步使用笔记本。
在我们向你展示优秀的Python库之前,我想快速概述一下如何通过Jupyter笔记本使用Unix命令。
尽管Python语言本身提供了使用Unix shell命令的方式,但Jupyter笔记本提供了一种更简单、更交互式的方式来使用Unix命令。我们只需在命令前加上感叹号!来执行它们,就像在Unix shell中执行一样。需要注意的是,Jupyter将使用你的默认Shell来执行这些命令,因此执行这些命令所需的任何调整都应基于你的Jupyter环境设置的操作系统。
现在让我们切换到我们的笔记本,执行一些我们回顾过的对数据科学家有用的命令。
让我们从ls开始。要显示与笔记本在同一目录下名为“Unix”的数据目录的内容,我们使用感叹号!加上ls命令。所以在我的笔记本环境和你的第3周文件夹中,你有一个名为“dot Unix”的文件夹。所以这里我们将去执行那个特定的shell命令,它说有一个Shakespeare.txt数据文件。
为了存储这个数据文件的名称,我将在笔记本中使用一个局部变量,就像我们通常在Python脚本中使用的方式。所以这里我们说filename = "Unix/Shakespeare.txt"。要显示这个变量,我们可以使用Unix方式,并在前面加上感叹号!来使用echo命令显示filename变量存储的值,或者简单地使用Python中的print函数,那么我们就不需要Unix那样的美元符号,我们只需将其作为Python变量使用。所以在这里,我们按Shift + Enter执行它,我们看到echo和print执行了相同的操作,输出了相同的行。
接下来,让我们显示文件的前几行和最后几行,以基本了解文件的头部和尾部。为此,我们将使用head命令。所以我可以快速用感叹号!执行head,加上-n 3显示前三行,加上$filename用于Unix变量解析,当我按Shift + Enter时,我们会看到前三行被显示出来。如果我想显示前30行,我可以改变那个数字为30,我会看到该数据文件的前30行,这是关于莎士比亚作品版权的一些免责声明。
对于底部的几行也是如此,查看文件的顶部和底部很重要,因为它让我们对文件有一些了解。为此,我们使用感叹号!加上tail命令。所以当我按Shift + Enter执行时,我会看到底部10行。让我们把那个数字改成大一点的,比如40,这样我们实际上可以看到一些关于莎士比亚的内容,所以我们看到在他的一部作品的结尾有一些内容。这应该告诉我们,它看起来是一个大文件,包含了莎士比亚的所有作品,在顶部和底部我们甚至看不到数据,只看到那些免责声明。
它有多大?我们可以使用wc(字数统计)来显示单词数、行数、字符数以弄清楚。所以这里我输入!wc $filename,当我这样做时,我得到大约124,000行、单词和字符。要只获取行数,我们使用带有-l选项的wc命令,这将在下一个输出行中显示。我们也可以使用管道和过滤器来做同样的事情,结合cat命令和wc命令。
现在让我们在文件中查找单词“parchment”的出现。正如你可能记得的,用于此的命令是grep。使用grep命令,它将显示所有至少包含一次单词“parchment”的行。所以这里我们输入!grep -i parchment $filename,这应该给我们所有包含“parchment”的行。当我按Shift + Enter时,基本上这些行中都有单词“parchment”在某个地方。
但具体有多少行?我们可以再次利用管道和过滤器。这次,让我们查找单词“liberty”而不是“parchment”,只是为了换个花样。我们要做的是:cat $filename通过管道传递给grep,在输出中查找“liberty”,并计算grep输出的行数,应该是这样的。如果我们没有wc -l,让我们暂时点击那个,我们只会得到“liberty”这个词,因为我们使用了-o选项。如果我将其传递给wc,我们刚刚移除的那个,我们会看到有71行。
因为我使用了带有-o选项的grep,我没有看到完整的行,只看到“liberty”这个词。现在让我们使用流编辑器sed来将所有出现的单词“parchment”替换为“manuscript”,并将结果写入一个名为“temp.txt”的新文件。我们可以这样做,然后在新的“temp.txt”文件中搜索单词“manuscript”。
所以我们将使用sed命令进行替换。这里我们说查找“parchment”,全局替换为“manuscript”,输入是我们的文件名,然后通过重定向写入“temp.txt”。我快速执行这个,现在我应该有“temp.txt”,你应该在你的第3周目录中都有“temp.txt”,你从那里打开了笔记本。
然后我们将去计算或查找“manuscript”在这个“temp.txt”中的出现次数。所以我们看到之前列出的“parchment”的相同行被列出,但“parchment”被替换为“manuscript”。当你快速将一组数据值替换为其他数据值时,这可能很有用。
现在让我们看看如何在管道和过滤器中一起使用sort命令和head命令。head在这里给我们前五行。正如你之前所见,cat $filename。如果我们对这五行进行排序,sort将根据ASCII数字按升序字符顺序排列这些行。
我们可以使用不同的排序选项。如果我们现在将这个输出传递给sort,通过一个管道,我们将按升序字符顺序排列这些行。就像这样。我们使用不同的排序选项,有时这里我们看到“liberty”这是按升序字符顺序排列的,意思是基于它们的ASCII数字。但如果我们想按第二组单词排序呢?所以“of”、“presented”和“releases”我们怎么做?通常我们处理的数据文件是空格分隔或逗号分隔的,所以我们将这些作为我们数据集中的列,也许想基于第二列排序。
为此,我们可以利用sort命令的选项,-f忽略大小写,我们现在说按第二列排序,加上命令的其余部分。我继续运行这个。我们看到“of”、“presented”和“releases”现在按正确的顺序排列。
接下来,我们将排序并找出唯一行的数量。记住,我们有大约124,000行文件在我们的数据文件“Shakespeare.txt”中。现在,让我们找出有多少唯一行,排序并将其传递给uniq命令,并计算行数,我们看到这里行数更少,有些行是重复的,看起来我们大约有110,000行。
为了总结一切,我们将计算数据文件中最频繁出现的单词。正如你在Unix练习中记得的,我们使用了一系列流编辑器命令进行一些处理,将空格替换为换行符,我们将从这里开始做同样的事情。在唯一字符计数和排序序列的末尾,我们将使用head命令检索前15个。
所以这与我们之前运行的管道过滤器完全相同,你只需要在开头加上感叹号!用于管道过滤器,正如你在这里看到的,然后如果你运行这个,它仍在处理,正如你看到星号在那里,它没有更新输入中的运行编号。我们会看到前15个被显示出来。所以发送head和sort命令是正确的,发送最后两个错误,但这是因为head在15行后停止。
那么我们如何将这个输出写入文件呢?我们会看到这个你知道这里。如果你运行这个,我们可以写入“count_vs_words.txt”。使用这个命令,按Shift + Enter,它仍在运行,正如你看到那里有一个星号。我们必须等一会儿让它运行完成。命令的标准输出将被写入“count_vs_words.txt”。记住sort给了我们一个错误,那是命令的标准错误,我们会在笔记本界面上看到,就像这里一样,所以这就是为什么我们在命令中留下了那个错误。
所以让我们cat显示“count_vs_words.txt”的内容。是前15个。我们没有移除空格,所以其中一些显示为那样。现在我们将利用Python的Matplotlib库来绘制莎士比亚作品中的前15个单词。所以这里再次,在第5周,我们将学习更多关于Matplotlib的知识,但这里我们看到一个使用Matplotlib的小脚本。就这样做,它显示“I”和空格,所有这些都很好地绘制在图表上。正如我之前提到的,我们将在第5周学习更多关于Matplotlib的知识。接下来,我们将讨论Python中的NumPy库,应该会很有趣。

在本节课中,我们一起学习了Jupyter笔记本的核心价值与基本操作。我们了解了Jupyter如何通过结合代码、文档和结果来促进数据科学工作流的记录与协作。我们实践了创建笔记本、运行Python代码、使用Markdown单元格进行格式化文档编写、管理笔记本文件以及通过!前缀执行Unix命令。这些技能为后续深入学习Python数据科学库打下了坚实的基础。
在本节课中,我们将学习NumPy的核心价值。NumPy是Python中进行科学计算的基础包,尤其对于数据科学至关重要。我们将探讨其关键特性、速度优势以及它如何成为其他流行数据科学库(如pandas)的基石。
NumPy提供了一系列用于科学计算的关键功能。
以下是其主要特性:
- 多维数组支持:NumPy的核心是
ndarray对象,用于表示向量和矩阵。在进行数据科学工作时,我们几乎时刻都在与矩阵打交道。 - 丰富的矩阵运算:NumPy提供了大量可在矩阵上执行的操作。这包括线性代数中的基本运算,如矩阵的加、减、乘,也包含优化的统计运算和快速傅里叶变换等。
- 广播机制:处理矩阵和向量时,确保维度对齐是较复杂的环节。NumPy通过支持“广播”机制简化了这一过程,使代码更易编写和阅读。
- 高性能与可扩展性:NumPy的速度通常足以满足生产代码的需求。若需进一步优化,它还提供了与Fortran、C和C++等优化编译代码库交互的能力。
上一节我们介绍了NumPy的功能特性,本节我们来看看数据科学家始终使用NumPy的三个核心原因。
以下是具体原因:
- 速度快:使用NumPy数组通常比使用Python列表快10倍以上。为实现这种速度,NumPy数组的大小是固定的(与可动态改变大小的列表不同),并且数组中的所有元素必须是同一类型(例如,全是整数或全是浮点数)。这种限制使NumPy数组比列表更节省空间,并开启了一系列内存和计算优化。
- 功能强大:如前所述,NumPy提供的操作非常实用。无论是计算向量或矩阵的平均值、矩阵乘法,还是基于索引或值选择矩阵的子集,都能轻松实现。即使后续使用pandas,你也会发现许多函数底层依赖于NumPy。
- 生态基石:Python中许多我们喜爱的包都依赖于NumPy。例如,我们即将学习的pandas就是构建在NumPy之上的。虽然pandas提供了比NumPy更高级的功能,但在某些时候你仍会直接使用NumPy的功能。
在接下来的视频中,我们将通过Jupyter Notebook深入探讨如何使用NumPy。本节我们将学习使用NumPy数组的基础知识。
在本节结束时,你应该能够创建一维和二维的ndarray,使用基本索引访问数组中的元素,并使用内置函数创建具有不同形状和初始值的数组。
就像使用Python的基本数据结构一样,我们将从创建ndarray和访问其元素开始。
首先,我们需要导入NumPy包。惯例是将其导入为np,然后使用np来访问该包中的函数。
import numpy as np
现在,让我们创建第一个一维数组(或称向量)。
an_array = np.array([3, 33, 333]) # 创建一个一维数组 print(type(an_array)) # 查看对象类型,应为 numpy.ndarray print(an_array.shape) # 查看数组形状,应为 (3,) print(an_array[0], an_array[1], an_array[2]) # 访问元素,输出 3 33 333
ndarray是可变的,这意味着我们可以更改其元素。
an_array[0] = 888 # 将第一个元素改为888 print(an_array) # 输出 [888 33 333]
需要注意的是,NumPy数组是类型严格的。尝试将整数元素赋值为字符串会导致错误。
# an_array[0] = 'a string' # 这行代码会引发错误
接下来,我们创建一个二维数组(即矩阵)。
another_array = np.array([[11, 12, 13], [21, 22, 23]]) # 创建二维数组 print(another_array) print(another_array.shape) # 输出 (2, 3),表示2行3列 print(another_array[0, 1]) # 访问第0行第1列的元素,输出 12 print(another_array[1, 0]) # 访问第1行第0列的元素,输出 21
NumPy最便捷的特性之一是提供了多种库函数,用于创建具有不同预设值和大小的数组。
# 创建一个2x2的零矩阵 zeros_array = np.zeros((2, 2)) print(zeros_array) # 创建一个2x2的矩阵,并用9.0填充 full_array = np.full((2, 2), 9.0) print(full_array) # 创建一个3x3的单位矩阵(对角线为1,其余为0) identity_matrix = np.eye(3) print(identity_matrix) # 创建一个1x2的全1矩阵(注意:这是二维数组,形状为(1,2)) ones_array = np.ones((1, 2)) print(ones_array.shape) # 输出 (1, 2) # 创建一个2x2的随机矩阵 random_array = np.random.random((2, 2)) print(random_array)
现在,你应该对创建NumPy数组和访问其中的元素感到比较自如了。在下一个视频中,我们将开始学习更高级的索引功能。
在上一节中,我们学习了创建和基本访问数组。本节我们将探讨访问和排列ndarray中数据的不同方法。
在本节结束时,你应该能够使用切片索引来访问ndarray的子集,并理解这种索引方式会创建对同一底层数据的第二个引用。
从字符串和列表的学习中,我们知道可以使用切片索引来获取这些数据结构的子集。我们可以使用类似的索引从NumPy数组中提取子区域。
首先,我们创建一个3x4的数组作为示例。
import numpy as np an_array = np.array([[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]])
接下来,使用切片索引提取前两行(第0行和第1行)以及中间两列(第1列和第2列)。
a_slice = an_array[:2, 1:3] # 行:0:2,列:1:3 print(a_slice) # 输出 [[12 13] # [22 23]]
重要概念:切片a_slice指向的是an_array中相同元素的内存地址。这意味着切片拥有自己的索引系统。
print(a_slice[0, 0]) # 输出 12,对应 an_array[0, 1]
修改切片中的元素会同时改变原始数组中的对应元素。
print(an_array[0, 1]) # 输出 12 a_slice[0, 0] = 1000 print(an_array[0, 1]) # 输出 1000
如果你希望切片是原始数据的一个副本,而不是引用,则需要显式地进行复制。
a_slice_copy = np.array(an_array[:2, 1:3]) # 创建副本 a_slice_copy[0, 0] = 9999 print(an_array[0, 1]) # 仍然输出 1000,原始数组未改变
# 获取第1行的所有列(返回一维数组) row_rank1 = an_array[1, :] print(row_rank1, row_rank1.shape) # 输出 [21 22 23 24] (4,) # 获取第1行的所有列(保持二维数组形状) row_rank2 = an_array[1:2, :] print(row_rank2, row_rank2.shape) # 输出 [[21 22 23 24]] (1, 4) # 获取第1列的所有行 col_rank1 = an_array[:, 1] col_rank2 = an_array[:, 1:2] print(col_rank1, col_rank1.shape) # 一维 [12 22 32] (3,) print(col_rank2, col_rank2.shape) # 二维 [[12] [22] [32]] (3, 1)
我们也可以使用索引数组来访问或重新排列大矩阵中的元素。
# 创建一个4x3的数组 an_array = np.array([[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]]) # 创建行和列索引数组 row_indices = np.array([0, 1, 2, 0]) col_indices = np.arange(4) # 等价于 np.array([0, 1, 2, 3]) # 使用索引对访问元素 for row, col in zip(row_indices, col_indices): print(f"({row}, {col}) -> {an_array[row, col]}") # 输出: (0,0)->11, (1,1)->22, (2,2)->33, (0,3)->41? (注意:col=3会越界,原示例有误) # 更正的例子:使用有效的列索引 col_indices = np.array([0, 1, 2, 0]) print(an_array[row_indices, col_indices]) # 输出 [11 22 33 41] # 通过索引数组修改元素 an_array[row_indices, col_indices] += print(an_array)
切片和数组索引是NumPy的核心,既方便又极其快速。在下一节中,我们将学习另一种基于数组值的索引方式。
在上一节中,我们学习了基于数组位置进行索引的各种方法。在数据科学中,我们经常非常关心数组中的值本身。
例如,我们可能想找出所有小于零或大于100的年龄,然后对这些值进行处理。为了进行这类数据清理,我们需要布尔索引。
在本节结束时,你应该能够使用条件索引来访问和排列ndarray中的相关数据。
首先,创建一个基础的示例数组。
import numpy as np an_array = np.array([[11, 12, 13], [21, 22, 23], [31, 32, 33]])
接下来,将条件与数组结合使用,创建布尔数组。
filter = (an_array > 15) # 创建布尔过滤器 print(filter) # 输出 [[False False False] # [ True True True] # [ True True True]]
现在,我们可以使用这个过滤器作为索引来获取原始数组中对应条件为True的值。
print(an_array[filter]) # 输出所有大于15的值
我们也可以将这两个步骤合并为一步。
print(an_array[an_array > 15]) # 一步完成条件筛选
我们可以组合更复杂的逻辑条件。
# 获取值在20到30之间的元素 print(an_array[(an_array > 20) & (an_array < 30)]) # 获取所有偶数值 print(an_array[an_array % 2 == 0])
我们也可以根据条件来修改数组元素。
an_array[an_array % 2 == 0] += 100 # 将所有偶数值增加100 print(an_array)
过滤器在许多数据科学操作和其他涉及矩阵的计算机科学算法中都非常有用。例如,图像的绿幕抠图就利用了过滤函数将背景中的绿色像素替换为你选择的另一幅图像。随着课程的深入,我们将在清理数据时更多地看到这种应用。
正如我们在NumPy介绍视频中提到的,每个ndarray都有自己的数据类型。本节我们将快速学习如何查看和设置该类型。此外,我们还将了解一系列数组运算。
在本节结束时,你应该能够检查和设置ndarray的数据类型,并熟练使用常见的数组函数。
数据类型很重要。如果尝试将整数数组中的元素赋值为浮点值,将会出错。
import numpy as np # 创建整数数组 int_array = np.array([1, 2, 3]) print(int_array.dtype) # 输出 int64 (或类似) # 创建浮点数数组 float_array = np.array([1.0, 2.0, 3.0]) print(float_array.dtype) # 输出 float64 # 显式指定数据类型 explicit_int = np.array([1, 2, 3], dtype=np.int32) print(explicit_int.dtype) # 强制转换输入数据的类型 # 将浮点数输入强制转换为整数(会截断小数部分) forced_int = np.array([1.7, 2.2, 3.9], dtype=np.int32) print(forced_int) # 输出 [1 2 3] # 将整数输入强制转换为浮点数 forced_float = np.array([1, 2, 3], dtype=np.float64) print(forced_float) # 输出 [1. 2. 3.]
让我们看一些ndarray上常见的算术运算。首先创建两个维度相同的数组。
x = np.array([[1, 2], [3, 4]], dtype=np.int64) y = np.array([[5, 6], [7, 8]], dtype=np.float64) print(x + y) # 逐元素相加,结果为浮点型 print(np.add(x, y)) # 使用add函数,结果相同 print(x - y) # 逐元素相减 print(np.subtract(x, y)) print(x * y) # 逐元素相乘 print(np.multiply(x, y)) print(x / y) # 逐元素相除 print(np.divide(x, y)) print(np.sqrt(x)) # 逐元素求平方根 print(np.exp(x)) # 逐元素计算e的x次方
对于许多运算来说,两个数组的维度需要对齐。在本例中,维度相同,所以没有问题。稍后在学习广播时,我们会看到这个约束有一定的灵活性。
我们已经学习了矩阵的一些基本运算,包括加、减、乘等。接下来,我们将深入探讨在整个课程中你会用到的一些更实用的函数。
在本节结束时,你应该能够使用ndarray的常用函数进行数据分析,包括统计、排序和集合运算。
作为一门数据科学课程,你会相当频繁地使用基本的统计运算。让我们看一些常用的统计函数。
import numpy as np # 创建一个2x4的随机数组 arr = np.random.randn(2, 4) print(arr) # 计算整个数组的平均值 print(np.mean(arr)) # 计算每一行的平均值 (axis=1) print(np.mean(arr, axis=1)) # 计算每一列的平均值 (axis=0) print(np.mean(arr, axis=0)) # 其他统计函数,如求和,使用方式类似 print(np.sum(arr)) print(np.sum(arr, axis=0)) # 计算每一行的中位数 print(np.median(arr, axis=1))
接下来,看看内置的排序函数。
# 创建一个有10个随机元素的数组 arr = np.random.randn(10) print("原始数组:", arr) # 创建副本并排序(不改变原始数组) arr_copy = np.copy(arr) arr_copy.sort() print("排序后的副本:", arr_copy) print("原始数组未变:", arr) # 原地排序(改变原始数组) arr.sort() print("原地排序后的原始数组:", arr)
在深入集合运算之前,先快速提一下unique函数。
arr_with_duplicates = np.array([1, 2, 1, 4, 2, 1, 4]) print(np.unique(arr_with_duplicates)) # 输出 [1 2 4]
我们实际上可以在ndarray上使用集合运算。
set1 = np.array(['desk', 'chair', 'bulb']) set2 = np.array(['lamp', 'bulb', 'chair']) # 交集 print(np.intersect1d(set1, set2)) # 输出 ['bulb' 'chair'] # 并集 print(np.union1d(set1, set2)) # 输出 ['bulb' 'chair' 'desk' 'lamp'] # 差集 (在set1中但不在set2中) print(np.setdiff1d(set1, set2)) # 输出 ['desk'] # 检查set1的每个元素是否在set2中 print(np.in1d(set1, set2)) # 输出 [False True True]
以上是我们想在视频中强调的最关键的函数。在下一个视频中,我们将深入探讨广播机制。
扩展学习:
ndarray还有大量其他有用的函数。我们鼓励你自行探索笔记本中提供的更多示例,包括如何对矩阵和向量执行点积和內积、如何对多维数组求和、如何使用逐元素函数和获取矩阵转置、如何使用随机数生成、如何合并数据集、如何使用where函数等等。

在本节中,我们将讨论广播。广播是NumPy中较高级的特性之一,它可以使你的数组操作更加方便。
在本节结束时,你应该能够运用广播来对不同大小的ndarray执行操作。
假设你有一个多维数组A,你想将数组B中的元素加到A的每一行上。这两个数组的大小不匹配。为了解决这个问题,你可能会尝试找出如何将B复制三次以便进行计算。这种大小不匹配正是广播旨在解决的问题。
让我们给矩阵和数组赋一些值。

import numpy as np A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) B = np.array([10, 20, 30])
现在,直接尝试将A和B相加。
print(A + B) # 输出: # [[11 22 33] # [14 25 36] # [17 28 39]]
广播机制会尝试找出你想要相加的维度。由于A的列数(3)与B的列数(3)匹配,它会自动执行你期望的计算:将B加到A的每一行上。值得注意的是,B保持了其原始形状,这正是广播的美妙之处——不涉及复制,因此这是一个内存和计算效率都非常高的操作。
在深入笔记本示例之前,我们先了解一下广播的规则(直接来自NumPy文档)。这些规则很可能符合你的直觉。
在两个ndarray之间,维度要么需要匹配,要么其中一个是标量。广播从匹配尾部维度开始,然后向前推进。标量值也适用,因为标量值有一行一列,所以加一个标量本质上也是使用了广播。
现在让我们看一些更具体的例子。
# 示例1:将 (3,1) 数组广播到 (4,3) 数组的每一行 start = np.zeros((4, 3)) # 4x3的零矩阵 add_rows = np.array([[1], [0], [2]]) # 3x1数组 print(start + add_rows) # 输出:每一行都加上了 [1, 0, 2] # 示例2:将 (4,1) 数组广播到 (4,3) 数组的每一列 add_cols = np.array([[1, 0, 2, 3]]).T # 创建1x4数组并转置为4x1 print(start + add_cols) # 输出:每一列都加上了 [1, 0, 2, 3]^T # 示例3:标量广播(最简单) print(start + 1) # 所有元素加1
广播需要一些练习和耐心,在操作时画出矩阵会很有帮助。
在本课程一开始我们就说过,NumPy因其速度和功能而受到重视。到目前为止,我们一直专注于展示NumPy的众多操作和内置功能。但现在,我想花点时间向你展示NumPy数组与Python列表之间的基本速度对比。
在本节结束时,你应该能够描述ndarray相对于列表的速度优势。
虽然我可以谈论ndarray的优势,特别是它们更节省空间和内存优化,但我认为亲眼看到速度差异更有说服力。
我们将使用timeit来测量代码的执行时间。我们计划使用包含100万个元素的数组或列表,并重复执行求和所有元素的操作1000次。
import numpy as np import timeit # 测试 NumPy 数组 size = n_array = np.arange(size) # 创建0到size-1的数组 print(type(n_array)) def numpy_sum(): return np.sum(n_array) time_numpy = timeit.timeit(numpy_sum, number=1000) print(f"NumPy 数组求和1000次耗时: {time_numpy:.6f} 秒") # 测试 Python 列表 a_list = list(range(size)) print(type(a_list)) def list_sum(): return sum(a_list) time_list = timeit.timeit(list_sum, number=1000) print(f"Python 列表求和1000次耗时: {time_list:.6f} 秒") print(f"NumPy 比列表快大约 {time_list/time_numpy:.1f} 倍")
运行结果会显示,对于这个包含100万个元素的求和操作,NumPy数组的速度比Python列表快一个数量级(大约10倍或更多)。这对于一个相当小的数组来说已经很明显,如果我们处理更大的数据集,将会看到更大的影响。

本节课中,我们一起学习了NumPy的核心价值。我们探讨了NumPy作为科学计算基础包的关键特性,包括多维数组支持、丰富的运算、广播机制以及高性能。我们深入实践了如何创建和操作数组,使用了基本索引、切片、布尔索引和数组索引。我们还学习了数组的数据类型、常用算术、统计、排序和集合运算。最后,我们通过对比实验,直观地感受到了NumPy数组在速度上相对于Python列表的巨大优势。掌握NumPy是步入Python数据科学世界至关重要的一步,它为后续学习更高级的工具(如pandas)奠定了坚实的基础。

在本节课中,我们将分析一个来自名为WiFIR的研究项目的卫星图像数据集,并使用NumPy库进行分析。WiFIR是一个用于野火分析的集成系统,能够处理不断变化的城市动态和气候。通过本课,你将能够描述卫星图像数据是什么,以及它如何帮助对抗野火,并应用NumPy中的基本方法进行图像处理。
上一节我们介绍了WiFIR系统及其目标。本节中我们来看看卫星图像数据本身及其在地球科学中的应用。
在这节课中,我们将使用一个来自Landsat的卫星图像,该图像可用于分析地球表面。对于任何地球科学应用来说,以地图、照片或卫星图像形式分析视觉图表是非常常见的,正如我们在此图像中看到的那样。
许多其他数据科学应用也会涉及某种形式的图像处理。我们需要对图像格式有基本的了解,并能够在Python中处理图像,才能在这个广阔的领域入门。
因此,在我们切换到笔记本进行进一步练习之前,让我们回顾一下关于图像处理和地球科学应用的几个关键点。
简单来说,计算机将图像存储为微小正方形的马赛克。这就像古老的马赛克艺术形式,或者今天孩子们玩的熔珠套件。
如果这些方形图块太大,就很难形成平滑的边缘和曲线。你使用的图块越多、越小,图像就越平滑,或者我们说,像素化程度越低。这有时被称为图像的分辨率。
请注意,矢量图形是一种不同的图像存储方法,旨在避免与像素相关的问题。但我们现在暂时不讨论这些,因为即使是矢量图像,最终也是以像素马赛克的形式显示的。
这些正方形中的每一个都称为一个像素。每个像素只有一种颜色,由一组数字定义。

描述每个像素的一种简单方法是使用三种颜色的组合,即红色、绿色和蓝色。这就是我们所说的RGB图像。

在RGB图像中,每个像素由三个8位数字表示,分别关联到红色、绿色和蓝色的值。这三个数字的组合反过来会给我们一个特定的像素颜**调。
由于每个数字都是8位数字,因此值的范围是0到255。例如,黄色值可以通过RGB值(255, 255, 0)来识别,正如我们在此图中看到的。
如果所有三个值都处于全强度(即255),则显示为白色;如果所有三个颜色都被减弱或值为0,则显示为黑色。
由于每个值可以有256种不同的强度或亮度值,因此总共有1680万种色调。
在Python中,RGB图像是形状为 高度 × 宽度 × 3 的NumPy数组,对应每个RGB层。
现在你已经对彩色图像在Python中的存储方式有了基本背景,我们可以更深入地研究WiFIR数据。
在接下来的视频中,我们将浏览一个笔记本,首先从NASA的Landsat卫星导入一张高分辨率图像。我们将主要使用NumPy函数、过滤技术以及一些地球科学领域的知识来分析这张图像。
那么,我们最终如何将这些分析结果和预测性火灾模型使用起来呢?我们将把这个燃料数据集,作为我们用于火灾建模的众多数据集之一。我们在此图中看到,如何将许多实时分析结果作为火灾建模的一部分。一旦与其他信息来源集成,这种更好、更自动化的燃料模型可以带来更准确的结果。
我们为你提供了一个短视频,进一步解释了所有这些数据集是如何使用数据科学进行集成和分析的,作为本课开始笔记本之前的下一段视频。如果你想直接跳到笔记本,请随时跳过它。
这里我们为你提供了一个NumPy笔记本,用于使用NumPy进行基本的图像处理。
如果你正在寻找该笔记本,你需要进入你的week3文件夹,找到“卫星图像分析”笔记本,并请定位包含数据的WiFIR文件夹。
我们选择卫星图像是因为卫星图像是一种非常有趣的图像数据来进行此练习。
以下是操作步骤:
- 导入必要的库
像我们的其他笔记本一样,我们将首先导入NumPy、SciPy和Matplotlib。
import numpy as np import scipy import matplotlib.pyplot as plt - 读取卫星图像
接下来,我们将使用imread函数将卫星图像作为NumPy数组读取。这个函数由Python提供给我们。
from scipy import misc photo_data = misc.imread('WiFIR/3band_AustinTX.tif')如果我们查看这个数据的类型,我们会看到它是一个NumPy数组。正如我们之前讨论的,这是一张RGB图像,类型是NumPy数组,因为它将被转换为一个三维数组。
- 显示图像
让我们在笔记本中绘制图像,以便在处理之前查看它。我们首先设置一个15x15的图形大小,你可以根据笔记本的分辨率等调整这个大小。然后我们使用imshow函数来显示它。
plt.figure(figsize=(15,15)) plt.imshow(photo_data) plt.show()我们看到图像显示出来了,很好,现在我们看到了彩色的卫星图像。
- 检查数组维度
但NumPy数组的维度是多少?我说过它是三维的,我们需要使用shape函数,type函数不足以显示维度。
print(photo_data.shape)输出类似于
(3725, 4797, 3)。3725是高度,4797是宽度,它有3层对应RGB。 - 理解颜色含义
这张图像有一个有趣的地方。像许多其他可视化一样,每个RGB层中的颜色都代表着某种含义。
- 红色强度表示像素中地理数据点的海拔高度。
- 蓝色强度表示坡向的度量。
- 绿色强度表示坡度。
对于训练有素的地球科学家或野外建模者来说,这些颜色有助于以更快、更有效的方式传达这些信息,而不是显示数字。如果只看这些数字,解释起来会很困难。但仅仅通过观察这张彩色图像,训练有素的眼睛已经可以分辨出海拔、坡度和坡向。这就是为这些颜色加载更多科学含义的想法。
- 探索图像数据
加载图像后,我们可以开始探索它。让我们检查它的大小,我们将使用NumPy数组的size函数。像我们做的许多其他事情一样,我们将检查数据值的最大值和最小值,以及平均像素值。
print(photo_data.size) print(photo_data.min()) print(photo_data.max()) print(photo_data.mean())这些值很重要,因为8位颜色强度不能超出0到255的范围。
- 访问和修改像素值
使用NumPy数组(图像数据变量photo_data),我们也可以检查图像中某个像素的RGB值。例如,我们将检查第150行第250列。我们将看到该像素的RGB值。
print(photo_data[150, 250])输出类似于
[17, 35, 255],所以这个特定像素有很多蓝色。我们也可以只选择其中一个值,比如如果我们想选择绿色。
print(photo_data[150, 250, 1])索引1会给我们绿色值,所以我们会看到绿色值是35。
同样,我们可以通过为像素分配特定值来更改每个像素的单个值。例如,我们将把刚刚处理过的像素(第150行第250列)设置为0。
photo_data[150, 250] = 0我们将所有三个层都设置为0,所以这个像素会变成黑色。当然,我们可以再次使用图形绘制和
imshow来显示这个图像,但由于我们只改变了一个像素,所以变化不会很明显。我们也可以尝试改变一个像素范围。让我们尝试做一些更大的改变,将绿色层的值设置为全强度,对于行范围200到800内的所有列。
photo_data[200:800, :, 1] = 255我们显著增加了这些像素的绿色值。它再次从第200行到第800行,针对所有列。所以你可能会期望看到一个水平范围或一个比周围像素更绿的大条带。
类似地,我们可以设置范围。我将重新加载图像,因为我们刚刚更改了图像,我想将其设置回原始状态。
photo_data = misc.imread('WiFIR/3band_AustinTX.tif') photo_data[200:800, :, :] = 255你期望看到什么?我希望它是白色的,因为所有三种颜色都完全增强了,所以显示为白色。当然,我们也可以通过将所有三个层设置为0而不是255来做成黑色,这样我们就减弱了所有颜色。
现在请暂停视频,尝试调整范围和RGB值,让自己熟悉这种表示法。在笔记本的其余部分,我们将大量使用这种表示法,因此理解到这一点为止的所有内容非常重要。
现在,让我们转到下一张幻灯片,我们将开始使用逻辑运算符创建一个相同大小的布尔NumPy数组,以便检查这些像素的值。
像之前一样,我将首先重新加载图像到photo_data,因为我们又更改了它一点,我想为笔记本的其余部分使用原始图像。
- 创建布尔掩码
我们将使用比较操作来选择所有小于50的值。这将创建一个与原始数组形状相同的布尔NumPy数组。
photo_data = misc.imread('WiFIR/3band_AustinTX.tif') low_value_filter = photo_data < 50 print(photo_data.shape == low_value_filter.shape)我们生成了
low_value_filter,使用全局比较运算符来筛选所有小于50的值。 - 应用过滤
现在我们如何使用这个low_value_filter数组进行过滤呢?也许我们现在将这个数组用作索引,将这些低值设置为零。
plt.figure(figsize=(15,15)) plt.imshow(photo_data) plt.show() photo_data[low_value_filter] = 0 plt.figure(figsize=(15,15)) plt.imshow(photo_data) plt.show()这是我们的第一张图像,原始图像,下一张我们应用了过滤器。虽然肉眼不太明显,但图中强度有一点变化。
让我们回到过滤器并将其设置为200,然后做同样的事情。我们会看到图像真的会改变。
low_value_filter = photo_data < 200 photo_data[low_value_filter] = 0 plt.imshow(photo_data) plt.show()现在我们的低值过滤器并不完全是低值过滤器,因为我们过滤的是相当高的值(任何小于200的值,最大值只有255)。我们应用这个过滤器到
photo_data,这是我们的原始图像,我们看到下一张图像过滤掉了许多RGB值低(在本例中小于200)的像素。
现在,让我们使用行和列索引做一些更多的操作。在这里,我们将创建范围数组。
- 创建范围数组
我们将创建两个范围数组,一个用于行,一个用于列。
rows_range = np.arange(photo_data.shape[0]) columns_range = np.arange(photo_data.shape[1])这些数组将是NumPy数组类型。
- 使用范围数组设置像素
在下一个单元格中,我们使用这些范围数组将与它们关联的像素值设置为所有颜色的全强度,这意味着使这些像素的颜色变为白色。
photo_data[rows_range, columns_range] = 255 plt.imshow(photo_data) plt.show()现在如果我们绘制这个图像,我们会看到一条白色的对角线。因为我们有一个行的范围数组和一个列的范围数组,我们将这些作为索引设置给这个矩阵,并分配特定的像素(如(0,0), (1,1), (2,2)等)为255。我们看到那是一条从0到大约3700范围(图像中的行数)的对角白线。
现在我们将做一些更有趣的事情。让我们尝试切出一个半径为总行索引一半的圆。
- 计算中心点和距离
为此,我们需要计算所有点到中心的欧几里得距离。如果你有一个三角形,其长边是从点(x1, y1)到中心(X, Y),那么圆的半径就是任意点的欧几里得距离。所以,x² + y² 应该小于 r²。
我们如何做到这一点呢?让我们看看如何转换其含义,并尝试为圆外的所有点创建一个为真的过滤器,圆内的所有点为假。
在接下来的代码块中,我们首先获取
photo_data的形状,并分配行数、列数和层数。total_rows, total_columns, total_layers = photo_data.shape - 使用
ogrid创建网格
接下来,我们使用ogrid函数来帮助我们向量化从中心的距离,这将是两个变量的函数。ogrid返回一个NumPy数组。
x, y = np.ogrid[:total_rows, :total_columns]ogrid将给出总行数的范围和总列数的范围,它将给我们两个向量x和y。x将具有我们在这里给出的总行数,y将具有总列数。ogrid是一种在单行中创建多维NumPy数组操作的紧凑方法。 - 计算圆形掩码
接下来,我们现在将计算中心点X和Y,并将它们称为center_row和center_column(总行数除以2,总列数除以2)。我们将使用我们的两个向量x和y来计算距离大于我们试图创建的圆的半径的点。
center_row = total_rows / 2 center_column = total_columns / 2 distance_from_center = np.sqrt((x - center_row)2 + (y - center_column)2) radius = total_rows / 2 circular_mask = distance_from_center > radius我们使用我们的中心行和x向量来创建从中心距离的矩阵。我们将确保这个矩阵中所有大于半径的值都为真。我们将其分配给
circular_mask。如果我们打印
circular_mask,只会看到边缘。那些边缘的矩阵值确实为真。但如果我们对图形的中心部分(行1500到1700,列200到2200)提供一个范围查询,我们会看到那些点或像素确实是假的。因此,我们能够识别出圆内和圆外的东西。 - 应用圆形掩码
现在我们准备好了圆形掩码。我们将继续过滤图像。我重新加载那个原始图像,并使用photo_data和我们生成的圆形掩码来过滤它,将所有那些值分配为0(黑色)。
photo_data = misc.imread('WiFIR/3band_AustinTX.tif') photo_data[circular_mask] = 0 plt.imshow(photo_data) plt.show()想想你会期望在我们的原始图像中看到什么。当我们绘制这个图像时,我们会看到我们的图像被很好地切成了一个圆形。
现在让我们将这个圆形掩码与另一个掩码结合起来,该掩码现在遮罩图像的下半部分。
- 创建上半部分掩码
所以,它低于图像中任何低于那个中心行点的点。我们将使用相同的技术在这里创建一个掩码。在x向量中,任何低于图像中心点的行(即任何x小于center_row的行)将被过滤。所以,任何x小于center_row的行(即上半部分)将为真。我们称那个过滤器为half_upper。
half_upper = x < center_row - 组合掩码
然后,我们将使用NumPy的logical_and函数来组合这两个过滤器。我们将half_upper和circular_mask作为两个过滤器提供给这个掩码。所以,我们正在对每个行-列索引处的真假值进行“与”操作。我们应该得到一个过滤器,为上半部分和圆形周围的部分给出真值。
half_upper_circular_mask = np.logical_and(half_upper, circular_mask) - 应用组合掩码
现在,如果我们分配这个half_upper_circular_mask过滤器,并将该过滤器中为真的点分配值为255。这些部分在结果图像中应该显示为白色。
photo_data[half_upper_circular_mask] = 255 plt.imshow(photo_data) plt.show()我们看到圆形周围在上半部分的任何东西都是白色的。我们本可以使用随机值,但需要导入random。所以请暂停并在我们导入random的地方取消注释。这个随机分配。你应该看到强度值的效果。当我们使用随机整数时,我们仍然在这里选择200到255之间的高强度值。但颜色会随机变化,不会是纯白色,而是会有白色的阴影。所以请暂停视频并尝试那一行,确保你理解那部分。
现在让我们记住这张图像中每个RGB层的含义。记住红色代表海拔或地理点的高度。
现在我们知道如何创建掩码,我们将通过选择红色层(我们三层矩阵中的第0层)来创建一个,也许我们会选择强度高于150的任何东西。
- 创建红色掩码
我在这里做的是:我重新加载图像,并开始创建红色掩码。我取photo_data并选择第0层(我们的红色层)的所有行和列。红色掩码将为photo_data中红色层值小于150的所有值提供真值。
photo_data = misc.imread('WiFIR/3band_AustinTX.tif') red_mask = photo_data[:, :, 0] < 150 - 应用红色掩码
让我们使用这个掩码为所有这些点分配零。
photo_data[red_mask] = 0 plt.imshow(photo_data) plt.show()我们只显示高海拔区域,我们的图像实际上变化很大,因为我们只显示了红色强度为150或更高的图像中的点。
- 创建绿色和蓝色掩码
接下来在笔记本中,我们做同样的事情来找到高坡向和高坡度,我们在这里唯一改变的是我们改变绿色和蓝色层。请运行那两个代码单元格,观察你的图像如何变化。它不应该是相同的图像。它应该根据地势、坡度和坡向而有差异。

最后,让我们使用逻辑“与”操作,为具有高海拔、高坡向和低坡度的点创建一个复合掩码,就像我们为圆形和上半部分所做的那样。
- 定义各个条件掩码
我们像之前一样做红色掩码,绿色掩码用于大于100的点,蓝色掩码用于小于100的点。
red_mask = photo_data[:, :, 0] > 150 green_mask = photo_data[:, :, 1] < 100 blue_mask = photo_data[:, :, 2] > 100 - 组合条件
我们的最终掩码将使用logical_and函数组合这三个条件。
final_mask = np.logical_and(red_mask, np.logical_and(green_mask, blue_mask)) - 应用并显示结果
如果你在这里绘制图像,你会看到那些值,图像再次从原始图像变化了一点,我们过滤掉了具有这三个条件的点。
photo_data[final_mask] = 0 plt.imshow(photo_data) plt.show()
希望你喜欢这个笔记本。
作为练习,我建议你使用一张自己的照片,并创建一些有趣的过滤器来制作你自己的Instagram风格效果。
在本节课中,我们一起学习了:
- 卫星图像数据的基本概念及其在野火分析等地球科学中的应用。
- 计算机如何以RGB像素阵列的形式存储图像。
- 如何使用NumPy读取、显示和探索图像数据。
- 如何访问和修改图像中的单个像素或像素区域。
- 如何创建和应用布尔掩码来过滤图像,例如基于强度阈值或几何形状(如圆形)。
- 如何组合多个掩码来创建复杂的图像过滤效果。
- 如何利用图像中RGB通道的特定科学含义(如海拔、坡度、坡向)进行有针对性的分析。
通过掌握这些基本技能,你已经为在Python中进行更高级的图像处理和数据分析打下了坚实的基础。

在本节课中,我们将聚焦于Pandas库的价值。Pandas是Python中主要的数据分析库。通过本视频的学习,你将能够描述Pandas对数据科学和Python的价值,强调Pandas的关键数据结构,并讨论Pandas因其广泛的分析能力而被广泛采用的原因。
Pandas库提供了一系列对数据分析友好的功能,这使其成为最受欢迎的数据科学工具之一。Pandas构建于NumPy之上,因此NumPy的大部分优势仍然适用。然而,Pandas独特地支持以直观的方式摄取和操作异构数据类型。
Pandas还支持使用merge和join操作合并大型数据集。它提供了一个非常高效的库,用于拆分数据集、进行转换和重新组合。
Pandas提供的另一个重要功能是其可视化能力。通过DataFrame内置的函数,绘图数据变得非常简单。
使用简单函数进行描述性统计是Pandas的另一个优点。这个功能极大地简化了探索性数据分析以及结果的沟通。
此外,Pandas库通过其原生方法有效地处理时间序列数据,提供了摄取、转换和分析时间序列数据的能力。
使用Pandas的其他好处包括:利用原生方法处理缺失数据和数据透视,轻松的数据排序和描述能力,快速生成数据图,以及用于快速图像处理和其他掩码操作的布尔索引等。
Pandas之所以能实现这些功能,得益于两种主要的数据结构:Pandas Series 和 Pandas DataFrame。
Series是一个一维的、类似数组的对象,它为我们提供了多种索引数据的方式。Series的行为类似于NumPy的ndarray,但它支持多种数据类型,如整数、字符串、浮点数、Python对象等。由于它与数组的相似性,它可以作为大多数NumPy函数的有效参数。轴标签统称为索引,我们可以通过这些索引标签来获取和设置值。因此,Series在这方面就像一个固定大小的字典,但它非常灵活。
尽管Series是一个灵活的数据结构,但使用更广泛的数据结构是Pandas的DataFrame。DataFrame是一个二维的、可变的数据结构,支持异构数据,并为行和列提供了带标签的轴。算术运算可以在行和列标签上进行。我们可以将其视为Series对象的容器,其中每一行都是一个Series。
如果你正在寻找执行某些数据转换的功能,很可能Pandas已经具备了。它提供了数据科学家所需的大部分主要数据整理能力,拥有活跃的开发者社区支持,并且功能在不断增长。我们认为Pandas在未来十年将在数据科学过程中扮演更重要的角色。
我们已经回顾了为什么Python中的Pandas库非常有用,并讨论了其中的两种主要数据结构。现在,让我们开始使用Pandas笔记本来回顾这些数据结构。
要跟随操作,请打开你第四周文件夹中的“Introduction to Pandas”笔记本。如果你准备好了,现在让我们尝试导入pandas模块。你会看到这一行代码:import pandas as pd。运行它,我们的笔记本就可以使用Pandas函数了。
在我们深入探讨Pandas函数的细节之前,让我们先回顾一下刚刚讨论过的两种主要数据结构。
首先,我们创建一个名为s的Series对象。它类似于NumPy数组,但我们可以定义索引标签,就像你在这里看到的那样,与数据一起定义。
s = pd.Series(data=[100, 200, 300, 400, 500], index=['Tom', 'Bob', 'Nancy', 'Eric', 'Sue'])
运行这段代码,Series将被创建。当我们输出s时,会看到数据数组被我们放入数据结构中的那些名称索引。因此,索引不再是0到4,而是一个具有定义为Tom、Bob、Nancy等索引的五元素Series。
虽然在这个例子中我使用清晰的格式定义了数据和索引,但我们也可以省略data=和index=,因为Pandas知道如何将这两个数组解析为Series数据结构。
与NumPy数组相比,Series的另一个特点是数据类型可以是异构的。所以,如果我将一些数据替换为字符串,这些值将会改变。
如果你在任何时候对索引感到困惑,可以像我们在这里做的那样显示索引:s.index,它会给你所有索引的列表。
我们可以使用方括号内的任何索引来访问该位置的数据。例如,s['Nancy']会指向值300。运行这个,我们确实得到300作为输出。或者,我们可以使用Series对象的.loc函数来获取某个位置的值。所以,如果我说s.loc['Nancy'],意思是给我由Nancy索引的位置的值,我们会看到输出是相同的。
现在,如果我们想访问多个位置,我们可以输入s[['Nancy', 'Bob']]。运行这个,它会给出由Nancy和Bob索引的两个数据值。
访问Series中数据的另一种方法是使用数字索引。这里我们访问s中的第4、3和1个元素。提醒一下,像所有其他数组索引一样,Series索引也从0开始。运行这个,我们会看到由Eric、Sue和Bob索引的位置被显示出来。
我们也可以使用.iloc函数通过输入.iloc[2]来实现同样的效果,它应该给出第二个索引(0,1,2)指向的值,即Nancy。
接下来,在这个笔记本中,你会看到索引的使用以及检查索引是否存在于Series中。所以,你可以快速检查由Bob索引的数据值是否存在于s对象中。我们也可以对Series使用Python操作,就像我们在NumPy中做的那样。这里我们将整个Series乘以2。
当我们这样做时,你会发现一些关于字符串的有趣现象。当我将字符串值乘以2时,它们会被重复两次。
我们也可以对Series数据元素的平方进行计算。但在这个例子中,如果我们对整个包含字符串的Series求平方,会得到一个错误,因为它不知道如何计算字符串的平方。所以,我们只取出数值部分(例如,Nancy和Eric)并重复相同的操作,你会看到在我们从Series对象s中取出的那部分Series上,我们可以计算这些平方。
接下来,我们将讨论Pandas DataFrame。创建DataFrame的方法有很多。我们通常只是将数据读取并摄取到DataFrame中,但在这样做之前,让我们手动创建DataFrame,并回顾一些使用这些DataFrame的简单方法。
让我们从由Series对象字典创建DataFrame开始。记住,我们正在向数据结构中添加另一个维度,所以我们需要标记每个Series对象。
d = { 'one': pd.Series([100, 200, 300], index=['apple', 'ball', 'clock']), 'two': pd.Series([111, 222, 333, 444], index=['apple', 'ball', 'cyril', 'dancy']) }
现在我们已经有了一个字典,我们可以将这些数据加载到一个名为df的DataFrame中。输入df = pd.DataFrame(d)并显示df的内容。
运行这个,我会看到生成了两列数据,字典中的Series被合并到了DataFrame中。注意,索引标签也被合并了,这导致了行标签为apple、ball、clock和dancy。而列标签是‘one’和‘two’。我们看到,第一列(标记为‘one’的Series)没有某些索引(如cyril和dancy),而第二列有。在那里,这些值将显示为NaN,表示“非数字”,表示在该Series中该索引没有值或该值未定义。
然后我们说df,输出以清晰的表格格式显示。但我们也可以打印df,这会稍微改变格式。如果你再次对这些列和索引标签感到困惑,你可以通过df.index和df.columns打印出来。
现在,我将在这里使用一些索引和列。如果你说pd.DataFrame(d, index=['dancy', 'ball', 'apple'], columns=['two', 'five']),我们选择了字典d中Series的索引子集(dancy, ball, apple)来创建DataFrame。我们添加了新的列,记得我的字典有‘one’和‘two’作为列,但我要求的是列‘two’和‘five’。当我们这样做时,我们选择了一个不存在的标签‘five’,所以该列中的所有值,正如我们在这里看到的,都将显示为NaN(未定义)。
我们也可以从常规的Python字典而不是一组Series对象创建DataFrame。这里我们有一个包含两个字典的数据数组。我们可以使用pd.DataFrame()函数将其加载到DataFrame中。运行这个,我们会看到Alex、El Dora、Imajo等标签被创建为列,而行标签是0和1,因为我们没有提供索引标签,所以将使用从0开始的数字索引。我可以为这个DataFrame提供一个索引标签数组。所以,如果我指定index=['orange', 'red'],同样的DataFrame将被创建,但行标签将是orange和red,而不是0和1。
就像我们之前做的那样,我们可以从字典中选择一些元素作为列,以限制我们处理的数据集。所以这里,我们创建DataFrame时只选择了‘Joe’, ‘Dora’, ‘Alice’这些列。我们将再次拥有由0和1索引的行,并且只有我们从原始数据集(数据字典)中选择的列。
现在,我们如何从DataFrame中获取数据以及如何使用这些基本的DataFrame操作?让我们回到我们原始的DataFrame df。我们可以使用列的标签从框架中选择一列。当我们说df['one']时,我们收到一个Series对象作为返回,由apple、ball等行索引,但显示的是这些列中的一列。
使用这些列,我们实际上可以创建新列,并动态地将它们添加或插入到DataFrame中。所以这里,我将在我的DataFrame df中创建第三列,我们称之为df['three'],它将是df['one']和df['two']中值的乘积。运行这个,我们会看到第三列被创建为第1列和第2列的函数。
在下一个例子中,我们有一个逻辑操作。我想添加另一个名为‘flag’的列,它不是一个像乘法那样的数值操作,而是一个逻辑操作,结果生成一个布尔值。我们将使用df['flag'] = df['one'] > 250。它会为第1列中大于250的值给出True。所以当你查看时,100和200是False,NaN本质上是False,但300是一个大于250的数字,所以它会被评估为True,而NaN我们无法与任何数字比较,所以是False。这样我们就有了名为‘flag’的第四列。
那么,我们如何从DataFrame中移除或删除数据呢?df.pop()用于返回并删除提供的列。所以如果我们说three = df.pop('three'),第3列作为一个Series对象被返回。当我再次显示df时,我们会看到第三列消失了,所以我们把它弹出来,赋值给一个变量,并永久地从我们的DataFrame中删除了第3列。
我们也可以使用del函数来删除列。所以这里我们看到del df['two']。运行这个并显示df,你会看到第二列(由‘two’索引)已经消失并从df中永久删除。然而,在这种情况下,我们没有得到输出显示,这与pop操作不同。
最后一个例子很有趣,它通过选择另一列的前两行来创建一个新列。我们在这里做的是df.insert(),所以我们正在向df(我们的DataFrame)中插入一些东西。我们将命名该标签为‘copy of one’。它会精确地将‘one’列复制到第二列。所以它将在DataFrame的末尾添加一个看起来完全像第1列的列,但其索引列的标签是‘copy of one’。这里,我们实际上稍微改变了一些东西。我们说的是获取DataFrame第1列中的前两个值,并将其赋值给一个名为‘one upper half’的DataFrame列。当我们这样做时,我们会看到该列的前两个值是第1列的值,而该列的其余行显示为未定义。
在本课的剩余部分,我们将使用电影数据集分析作为案例研究。在接下来的视频中,我们将提供一些使用Pandas进行数据摄取、生成数据描述性统计、数据清洗、子集化、过滤、插入、删除和聚合的示例,并向你介绍处理时间序列数据的基础知识。
在本课中,我们将专注于将数据导入Python。通过本视频的学习,你将能够描述Pandas提供的将数据导入内存的高效且易于使用的方法,识别诸如read_csv之类的用于将CSV(逗号分隔值)文件读入DataFrame的函数,并讨论Pandas可以直接导入的其他数据资源。
使用Pandas的最大优势之一是其能够从各种来源摄取各种数据类型和格式的数据。我们可以简单地说,Pandas为我们所有人简化了数据摄取。让我们看看其中一些数据格式和使其成为可能的函数。
最流行的数据格式之一是逗号分隔值,简称CSV。CSV是一种用于存储表格数据(如电子表格或数据库)的简单文件格式。可以使用Pandas的read_csv函数将CSV格式的文件作为DataFrame摄取到Python中。
JSON(JavaScript对象表示法)是一种用于结构化数据的格式,通常用于Web应用程序内部的通信。使用Python Pandas中的read_json函数,我们可以将JSON文件的结构和内容作为Pandas DataFrame或Series数据结构摄取。
HTML(超文本标记语言)是一种文件格式,用作每个网页的基础。使用read_html函数,HTML文档中的数据作为Pandas DataFrame列表存储。
SQL(结构化查询语言)用于通过查询与数据库通信,以插入、删除和选择感兴趣的数据。Pandas中的read_sql_query函数为我们提供了一种从关系数据库子集化并将数据加载到Python中的方法。类似地,我们可以使用Pandas的read_sql_table函数加载整个关系表,然后它将简单地以表格格式显示为Pandas DataFrame数据结构。
总之,将数据摄取到Python中并不总是容易的。Pandas使其成为一个直观的过程,并为数据科学家提供了工具来操作摄取的数据,以及关键的数据结构,以支持各种各样的数据格式。我们只列出了可以摄取到Python中的少数几种源类型,但如果你遵循本摘要幻灯片中提供的链接,还有更多示例。
我们现在进入刚刚回顾过的关于数据摄取的实战编码环节。在本笔记本的其余部分,我们将使用来自MovieLens网站的电影数据集。我们使用的数据集应该位于你第四周的文件夹中,名为“movielens”的目录下。请确保在我们开始之前找到它。
让我们查看该movielens目录的内容。这里我们将使用感叹号ls命令。我看到我有README.txt、genome-tags.csv、movies.csv以及该目录中的其他一些CSV文件。让我们实际查看其中一些文件的内容。我们将使用cat命令。运行cat movies.csv,这将需要一些时间。你会看到一些以逗号分隔的文件。在那个目录中,我们正在运行cat来显示movies.csv文件的内容,我们会看到里面有许多格式相似的行,它们有MovieID、Title和Genre的值,用逗号分隔。这就是我们所说的逗号分隔值,当我们将它们加载到DataFrame中时,这三个值(例如8、Tom and Hawk 1995、Adventure|Children)将被逗号分隔,并成为我们DataFrame中的列。
让我们检查这个输出的第一行。我们看到第一行是movieId、title、genres。这些是DataFrame将使用的数据列的标签。如果它们不存在,它将简单地以数字方式索引。现在,让我们通过将这个命令的输出通过管道传输到带有-l选项的wc命令来找出有多少部电影。我们看到这个电影数据库中大约有27,000多部电影。我们可以对tags.csv和ratings.csv做同样的事情,以确保数据存在,并理解其格式。
最快的方法是使用head -5命令来显示movielens数据文件movies.csv中的前五个元素。运行这个,我很快会看到第一行是movieId、title、genres,我们有第一部玩具总动员,前四行让我们对这个数据文件中的元素有了一个概念。我可以对tags做同样的事情,我们会看到tags.csv加载时的列标签是userId、movieId、tag和timestamp。我们在这个笔记本中感兴趣的另一个文件是ratings,让我们也显示它的前五个元素,我们看到列是userId、movieId、rating和timestamp。
现在我们将使用Pandas读取数据集并将其加载到DataFrame中。既然我们知道数据集是好的,并且理解了标签,我们将开始将数据加载到三个DataFrame中。
让我们从movies开始。这里我们将利用read_csv函数。我们需要将数据的分隔符指定为逗号。简单地说,movies = pd.read_csv('movies.csv', sep=',')。让我们显示movies的类型,以查看它是一个DataFrame,并使用head函数查看该DataFrame中的前五个元素。这里我显示了类型,以表明我们创建的movies DataFrame对象的类型是DataFrame类。我们使用DataFrame的head函数来显示前五行,这是默认值。对于head函数,我们可以指定行数。如果我想查看更多,我可以说head(15)。现在让我们以类似的方式将另外两个CSV文件加载到两个名为tags和ratings的新DataFrame对象中。我们对tags做同样的操作,读取我们检查过的movielens tags.csv,并用逗号分隔它们,我们使用head函数查看前五行。对ratings也是如此。我们会看到它正在内核中执行,一旦执行完成,in可能会更新为56。
这里我们对解析日期做了一些特殊处理。对于ratings,当我们谈到时间戳以及如何处理它们时,我们会在本课末尾再讨论。现在,对于当前的分析,正如我提到的,时间戳稍后再处理,我们将删除timestamp列,稍后再回来处理。所以,正如你从我们的DataFrame讨论中记得的,我们可以使用del ratings['timestamp'],给出该列的索引或标签,并将其从DataFrame中删除或删除。这样ratings和tags就不再有时间戳了。
好了,现在我们有了三个DataFrame。现在让我们回顾一下如何使用Series和DataFrame函数与对象交互。首先提取tags中的第一行。我们看到该第一行的类型是一个Series,如果我们打印该第一行,你会看到userId、movieId和tag是索引,用于我们拥有的值,例如userId 18。如果你对这些索引感到困惑,同样的规则适用于Series,你可以说row_0.index,它会给出索引值。你可以使用这些索引来获取由该标签索引的值。
你也可以进行一些布尔操作,你可以说'rating' in row_0,我能从中获取rating吗?正如你看到的,索引有userId、movieId和tag,所以那里没有rating,因此它将评估为False。
现在让我们使用一些DataFrame函数来处理DataFrame。我们已经见过head、index和columns。要提取一系列行,我们需要提供一个索引数组。所以我去tags,并给出索引。使用.iloc,因为记住,在这个DataFrame中有很多行。所以tags.iloc[[0, 11, 200]],我们给它一个包含索引0、11和200的数组作为那些行的名称。如果我们这样做,我们会看到我们可以提取这些值,并从技术上讲,从该DataFrame中选择这些值。
让我们在这里停下来,在继续实战编码环节之前,回顾一下下一个视频中的一些描述性统计。
在本讲座中,我们将专注于Pandas中一些用于生成描述性数据统计的有用函数。通过本视频的学习,你应该认识到Pandas对数据科学和Python的价值,描述Pandas执行数据统计分析的能力,并利用诸如describe之类的常用函数。我们还将探索Pandas中的其他统计函数,这些函数在不断演变。
摘要统计是用于捕捉数据集及其值各种特征的量,用一个数字或一小组数字表示。你应该为数据集计算的一些基本摘要统计是均值和标准差。Pandas通过describe函数自动完成这些。查看这些度量将让你了解数据的性质,并且它们可以告诉你数据是否有问题。例如,均值或最大值超出0到5范围可能指向我们的评分数据库中的一个不良数据集。
相关性或corr函数用于计算皮尔逊相关系数,可用于探索数据中不同变量之间的依赖关系。还有其他一些相关系数可用,如Kendall和Spearman相关性,Pandas也支持这些。作为旁注,负相关分数意味着如果x变大,则y变小;正相关意味着两个变量是相关的。我们将按原样使用corr函数,并等到下一节统计课再进一步探索相关性度量。
Pandas还提供了许多统计函数,你可以在整个DataFrame、DataFrame的一部分或单个列上执行。我们在这张幻灯片上将这些函数统称为func。只需用你喜欢的统计操作(如max、min、mode和median)替换它,你就会在Pandas中找到该函数。在这张幻灯片上,我们为你提供了一些关于均值和标准差输入和输出的基本信息,除了之前提到的那些。
Pandas还提供了在整个DataFrame或列上检查条件的能力。any和all函数然后分别应用于比较结果的对象,告诉我们是否有任何比较结果为真,或者是否所有比较结果都为真。
总之,Pandas提供了广泛的函数用于执行统计分析。虽然我们在这里通过回顾少数几个函数来浅尝辄止,但我建议你花一些时间探索本幻灯片提供的链接上的其他函数。
现在让我们花一些时间在我们的笔记本来回顾我们讨论的内容。好的,让我们开始查看我们数据集的简单描述性统计。我们承认你可能现在没有统计学背景,因此我们将专注于对这些函数进行非常轻量级的概述。你在这个微硕士项目中的下一节统计课将为你提供正确的背景知识,以便在Pandas中做更多统计操作。
现在让我们专注于本视频剩余部分中存储在ratings DataFrame中的rating列。正如你可能记得的,我们探索了这个数据集,评分在一个名为ratings的DataFrame中,就像你在这里看到的,前五行是该DataFrame中的索引。
让我们回到笔记本的描述性统计部分。我们将首先使用describe函数描述该列中的值。记住,describe函数会给我们一些关于数据的统计信息。我们看到那部分正在运行,我们得到了计数、均值、标准差等。该列中已定义或有用的值的计数显示有超过200万条评分记录,均值为3.53(我四舍五入为3.53)。标准差是衡量数据中离散度或变异性的指标。当涉及到误差时,较小的偏差是好的。然而,如果你的数据自然包含很多变异性,那是你对该数据观察的属性。然后我们有百分位数。如果50%是3.5,这意味着超过一半的评分是3.5或更低。类似地,这里75%的评分低于4.0。
请注意,我们本可以将整个DataFrame提供给describe。这里我们只提供了ratings列,但我可以快速复制粘贴这个,并描述整个DataFrame。虽然这通常非常有用,但在我们的数据中,userId和movieId是仅存储标识符而非测量值的列,因此我们可以安全地忽略它们的统计信息。这就是为什么我们选择只选择ratings列。
接下来,我将计算评分的均值,就像我们用describe做的那样,但这次它只会给我们那个均值。所以,如果你只想知道像你在这里看到的评分列那样的均值,你会调用mean函数,而不是调用整个describe函数。我们本可以再次在整个DataFrame上做均值。所以,如果我们那样做了,我们会有每列的均值。作为一行或表格,userId均值、movieId均值和ratings均值,就像这三个值会一个接一个地打印出来。
好的,接下来,我们将查看评分的最小值和最大值。运行这两个。我们说的是ratings['rating'].min()和ratings['rating'].max()。最小值是0.5,最大值是5.0,所以它们都在可能的良好值范围内,评分应该在0到5之间,我们看到最小值和最大值都在该范围内。
我们现在可以使用评分之间的标准差,再次提醒,这是它们与平均值的离散程度。所以它给出了这些评分中变异性或方差的度量。我们只需使用std函数。
了解最常见的评分值可能也很有趣。mode函数将给出这个信息,所以让我们在该评分列上运行mode函数。它计算每个值并找到最频繁的那个,所以在这种情况下,4.0是最频繁的评分,所以电影最常被评分为4.0。
在这一点上,我通常会查看具有可测量变量的列之间的相关性。在我们的数据集中,由于我们只有一列这种类型(rating),在整个DataFrame上运行相关函数不会给我们太多信息,不幸的是。但让我们这样做只是为了展示相关函数如何格式化其输出。所以这里我们只说ratings.corr()。运行这个,你会看到一个表格,行和列都有我们DataFrame中的列标签,所以这些理想情况下是每个可测量变量。该表格显示了每个变量与另一个变量的相关性分数。所以,正如你会记得的,这里的负相关分数意味着我们数据集中的那些特征是负相关的,所以一个上升,一个下降,增加一个意味着另一个减少。然而,正如我之前提到的,我们不会将这个结果纳入分析考虑,因为我们知道这些变量不可能相关。这也向你表明,要理解相关性,我们确实需要更多的信息。在其他笔记本中,我们将能够看到具有更确定性结果的相关性分数。
在下一行,我将检查是否有任何评分高于零。所以我们可以对整个列进行全局逻辑比较,得到一个Series对象,其中包含我们DataFrame每一行的布尔值。我们可以这样做:选择ratings['rating']并比较它是否大于五。它会给我们那个带有布尔值的Series对象。所以,如果一行中的值大于五,该行将为True;如果一行中评分列的值小于五,我们将得到False。运行这个,any会告诉我们filter1 Series中是否有任何值为True。在这种情况下,我们得到False作为输出,因为数据集中没有评分大于五。
为了理解那个过滤器是什么样子,让我们打印Series对象filter1。我实际上可以先打印它的类型,所以我们知道它是一个Series对象。让我们暂时去掉这个any。我们知道它是一个Series对象,对吧?所以让我们回去,不打印类型,而是打印过滤器对象。当我这样做时,我们看到那个对象,那个Series对象,是一个将评分与五进行比较的列,我们从0、False、False、False、False开始,一直到所有行。我们有大约200万行或更多,它们都是False,所以这就是我们评分数据库中的评分。从它的一小部分我们看到,确实没有True值。
我们也可以使用all函数做相反的事情。然后我们检查该列中的所有值是否为True。一个需要我们做的条件是,我们想确保所有评分值都高于零,并且我们已经查看了最小值,我们知道情况确实如此。所以我们将在这里运行一个条件:ratings['rating'] > 0,并将结果的Series对象赋值给filter2。我将检查filter2布尔Series中的所有值是否为True。快速运行这个,我们会得到True作为结果。
好的,现在我们已经探索了数据集的一些统计信息,让我们继续数据清洗,并首先回顾一下Pandas中的数据清洗函数。


在本节课中,我们一起学习了Pandas库的核心价值、其关键数据结构(Series和DataFrame)、如何创建和操作这些结构、如何从各种来源导入数据、以及如何使用Pandas进行基本的描述性统计分析。这些基础知识是进行更高级数据操作和分析的起点。

在本节课中,我们将学习Pandas库中用于数据清洗、可视化以及数据操作的核心功能。我们将了解如何处理现实世界中不完美的数据,如何通过可视化初步探索数据,以及如何使用切片、过滤、分组等操作来高效地处理和分析数据。
上一节我们介绍了Pandas的基础数据结构。本节中,我们来看看如何处理数据中常见的问题。现实世界中的数据通常是混乱的,可能存在缺失值、异常值或无效数据。例如,年龄字段出现负值,或者在Python数据框中出现NaN值。由于我们通常无法控制上游数据的收集方式,因此必须通过检测和纠正来解决这些质量问题,使数据最终可用于分析。
以下是Pandas中处理数据质量问题的几种方法:
- 替换无效值:可以将无效值或
NaN值替换为更合适的值。 - 填充数据:对于缺失值或数据缺口,可以尝试填充数据,而不是直接删除。通常需要根据领域知识或使用插值等技术来估算一个合理的值。例如,可以根据员工的入职年限来估算其缺失的年龄。
- 删除数据:根据探索性分析和统计分析的结果,可以考虑删除对当前任务不重要的字段或值。例如,在某些情况下可以删除异常值。
现在,我们来具体了解Pandas中一些重要的数据清洗函数。
replace函数可以全局更改数据框中的值。例如,可以将所有9999替换为0。
fillna方法会用已知值向前或向后填充缺失值。向前填充(method=‘ffill’)会用上方单元格的值填充NaN;向后填充(method=‘bfill’)则用下方单元格的值填充。
dropna函数用于删除包含缺失值的行或列。
- 使用
axis=0(默认)会删除任何包含缺失值的行。 - 使用
axis=1会删除任何包含缺失值的列。
interpolate函数可以对序列和数据框对象进行插值。默认方法是线性插值,即使用线性多项式来拟合数据点。Pandas也提供了其他插值方法。
总结:Pandas提供了多种处理缺失数据的简便方法。我们在此仅做入门介绍,更多方法请参考相关文档。
现在,让我们切换到Jupyter Notebook,看看上述讨论的实际应用。
我们首先检查电影数据集的形状:
movies.shape
结果显示约有27,000行记录。
接下来,我们检查数据集中是否存在空值:
movies.isnull().any() tags.isnull().any()
检查发现,tags数据框的tag列确实存在一些NaN值。
我们将使用dropna函数删除包含缺失值的行:
tags = tags.dropna() tags.isnull().any() tags.shape
操作后,所有列均返回False,表明缺失值已被成功删除。数据行数从465,564减少到465,548,共删除了16行。
在深入数据可视化课程之前,我们先简单介绍Pandas的绘图功能。Pandas底层使用Matplotlib库进行绘图。
要在Jupyter Notebook中内嵌显示图形,需要先执行以下魔法命令:
%matplotlib inline
Pandas的plot包提供了多种可视化图表:
- 条形图:
plot.bar(),每列以不同颜色表示。 - 箱线图:
plot.box(),展示数据分布,包括最小值、最大值和中位数。 - 直方图:
plot.hist(),显示数据分布,可揭示数据的偏态或异常离散情况。 - 折线图:
plot(),可以快速创建数据集的折线图。
总结:Pandas提供了一套多样化的绘图方法。通过可视化数据,我们常常能发现仅查看原始数据时不易察觉的规律。
现在,我们在Notebook中实践一些可视化和数据操作。
首先,为ratings数据框的rating列绘制直方图:
ratings[‘rating’].hist(figsize=(15,20))
接着,为rating列生成箱线图:
ratings[‘rating’].plot.box(figsize=(15,20))
箱线图清晰地展示了评分的平均值(约3.5)、最小值(0.5)和最大值(5)。
拥有高效的数据操作能极大加速所有使用这些操作的算法。本节我们将学习如何在Pandas中利用这些操作。


我们将使用一个示例数据框df进行演示。
- 选择列:通过列名直接选择,例如
df[‘sensor2’]。 - 选择行范围:使用索引切片,例如
ratings[0:10]选择前10行,ratings[-10:]选择最后10行。 - 条件过滤:基于条件筛选行。例如,
df[df.sensor2 > 0]会选出sensor2列值大于0的所有行。
- 添加列:通过赋值创建新列。例如,
df[‘sensor4’] = df.sensor32会创建一个名为sensor4的新列,其值为sensor3列的平方。 - 删除行:使用
drop函数并指定行索引。例如,df.drop([4])会删除索引为4的行。 - 删除列:使用
del函数。例如,del df[‘timestamp’]会删除名为timestamp的列。
groupby是一个非常有用的方法,用于获取数据框的聚合统计信息。例如,按student_id分组并计算每个科目的平均分:
df.groupby(‘student_id’).mean()
总结:Pandas拥有大量高效的方法来操作数据集。通过组合这些简单的操作,可以构建出复杂的数据分析流程,将原始数据转化为可分析、能获取深刻见解的形式。
现在,我们应用这些操作来处理电影数据集。
首先,查看tags表tag列的前几行:
tags[‘tag’].head()
选择movies表中的title和genres列:
movies[[‘title’, ‘genres’]].head()
对ratings表进行行切片:
ratings[0:10] # 前10行 ratings[-10:] # 后10行
使用value_counts统计tag列中各值的出现次数,并绘制前10个最常见标签的条形图:
tag_counts = tags[‘tag’].value_counts() tag_counts[:10].plot.bar()
创建一个过滤器,标记评分大于等于4.0的电影:
is_highly_rated = ratings[‘rating’] >= 4.0 ratings[is_highly_rated].tail()
创建一个过滤器,标记类型中包含“Animation”的电影:
is_animation = movies[‘genres’].str.contains(‘Animation’) movies[is_animation][5:15]
按rating分组,统计每个评分出现的次数:
ratings[[‘movieId’, ‘rating’]].groupby(‘rating’).count()
按movieId分组,计算每部电影的平均评分:
avg_rating = ratings[[‘movieId’, ‘rating’]].groupby(‘movieId’).mean() avg_rating.head()
按movieId分组,统计每部电影收到的评分数量:
rating_count = ratings[[‘movieId’, ‘rating’]].groupby(‘movieId’).count() rating_count.tail()

本节课总结:我们一起学习了Pandas中数据清洗的核心方法(如处理缺失值)、基础的数据可视化技巧(如直方图和箱线图),以及一系列高效的数据操作,包括数据切片、条件过滤、增删行列和数据聚合(groupby)。掌握这些技能是进行有效数据分析和数据科学项目的基础。

在本节课中,我们将学习如何使用Pandas库合并来自不同数据框的数据。数据通常分布在不同的位置和表格中,合并操作能让我们将这些数据整合起来,获得更全面的视角。我们将介绍几种合并数据的方法,并了解它们之间的区别。
处理数据框时,我们经常需要处理来自多个数据框的数据。常见的做法是将两个数据框中所需的数据合并到一个单一的数据框中,然后对这个新数据框执行操作。如果你熟悉数据库管理系统,这与其中的连接操作非常相似。
Pandas提供了多种方法来合并数据框。以下是几种主要的方法。
concat 函数可用于堆叠数据框,从而创建一个新的数据框。例如,将一个名为 Left 的数据框与自身连接。
import pandas as pd result = pd.concat([Left, Left])
如果提供给 concat 函数的两个数据框拥有不同的列,那么生成的数据框将包含两个数据框的所有列。在这种情况下,原始数据框中不存在的列,其单元格将填充为 NaN(缺失值)。
除了 concat,我们还可以使用 append 函数将一个数据框附加到另一个数据框。它的行为与 concat 函数类似,但它是数据框本身的一个方法。
result = left.append(right)
与首次使用 concat 类似,我们可能会再次得到许多空单元格。
能真正将两个数据框组合起来的操作是 merge。使用 merge 操作的好处在于,它可以消除所连接数据框之间的重复列。
result = pd.merge(left, right, on=['key1', 'key2'], how='inner')
它的行为非常类似于使用内连接的 concat,但会剔除我们之前遇到的重复列。
虽然这些方法根据情况各有其用途,但在尝试合并来自多个不同来源的数据时,我发现自己经常使用 merge 操作,尤其是当这些数据共享相同的键时。
现在,让我们通过一个实际的编码会话来了解这些功能。我们将使用电影和标签数据框。
首先,我们查看一下 tags 和 movies 数据框中的列。
print(tags.columns) print(movies.columns)
两个数据框都有一个 movieId 列。我们可以使用这个 movieId 列对这两个数据框执行内连接合并。
merged_df = movies.merge(tags, on='movieId', how='inner') print(merged_df.head())
这样,我们就将标签数据和电影数据合并到了一个数据框中。
接下来,让我们尝试将目前所学的知识综合运用起来。
首先,我们根据 movieId 列对所有评分进行分组,并计算平均评分。
avg_ratings = ratings.groupby('movieId', as_index=False)['rating'].mean() avg_ratings = avg_ratings.drop(columns=['userId']) print(avg_ratings.head())
现在,我们有了每部电影的平均评分。接下来,我们可以将这些平均评分与电影表合并。

box_office = movies.merge(avg_ratings, on='movieId', how='inner') print(box_office.tail())

合并后的 box_office 数据框包含了电影ID、标题、类型和来自平均评分数据框的评分。
我们可以利用合并后的数据应用过滤器。例如,设置一个筛选高评分电影的过滤器。
is_highly_rated = box_office['rating'] >= 4.0 high_rated_movies = box_office[is_highly_rated] print(high_rated_movies.tail())
再设置一个筛选喜剧类型电影的过滤器。
is_comedy = box_office['genres'].str.contains('Comedy') comedy_movies = box_office[is_comedy] print(comedy_movies.head())
我们还可以同时应用这两个过滤器,寻找高评分的喜剧电影。
high_rated_comedies = box_office[is_highly_rated & is_comedy] print(high_rated_comedies.tail())
通过将合并操作与之前视频中的技能相结合,我们可以看到这些功能非常强大。
字符串是常用的数据类型,因为数据科学经常涉及文本数据的研究。Pandas提供了许多有用的字符串操作。接下来,我们将回顾其中几个。
split 函数有助于围绕分隔符将数据分割成多个部分。例如,将数据框 df 中 city 列的值以下划线为分隔符进行分割。
df['city'].str.split('_', expand=True)
contains 函数提供了一种简单的方法来检查字符串是否包含给定的字符。
df['city'].str.contains('2')
使用 replace 操作,我们可以将一个子字符串替换为另一个。
df['city'].str.replace('_', '')
extract 函数将返回它找到的第一个正则表达式匹配项。它可以是从文本数据中构建新特征的快速方法。
df['title'].str.extract(r'$(d{4})$', expand=True)
电影表中的 title 和 genres 列都包含复合信息。例如,Toy Story (1995) 包含了年份,而 genres 则通过竖线字符将多个类型连接在一起。
让我们使用一些字符串操作来分离这些值。
首先,使用 split 函数将 genres 列中的每个值转换为单独的列。
movie_genres = movies['genres'].str.split('|', expand=True) print(movie_genres.head(10))
你还可以添加一个新列来检测每个类型是否为喜剧。
movie_genres['is_comedy'] = movie_genres.apply(lambda row: 'Comedy' in row.values, axis=1) print(movie_genres.head())
最后,让我们从 title 列中提取出年份作为一个单独的列。
movies['year'] = movies['title'].str.extract(r'$(d{4})$', expand=True) print(movies.tail())
现在,year 成为了 movies 数据框中的一个新列,这对于按年份进行分组等操作非常有用。
处理时间戳可能很困难,因为存在多种不同的时间数据格式和精度。在本节中,我们将介绍一些时间数据格式、结构和操作。
Unix时间通过计算自特定时刻(UTC时区1970年1月1日)以来的秒数来跟踪时间的进展。这是一个整数,我们需要将其转换为可读的日期和时间。
datetime64[ns] 是datetime的通用数据类型。我们的主要任务是将自1970年UTC时间以来的原始整数时间戳转换为上述日期时间格式之一,以便Python能以人类可读的格式呈现它。
我们可以使用 pd.to_datetime 函数快速将时间戳转换为Python格式。
tags['parsed_time'] = pd.to_datetime(tags['timestamp'], unit='s')
unit 参数在这里非常重要,因为它告诉函数输入的单位是什么。在这个例子中,输入列是来自 tags 数据框的 timestamp,输入的单位被声明为秒。输出存储在一个名为 parsed_time 的新列中。
一旦时间被转换为Python格式,你就可以用它来创建过滤器。例如,构建一个布尔过滤器,只选择在2015年2月1日之后的行。
greater_than_t = tags['parsed_time'] > pd.Timestamp('2015-02-01') selected_rows = tags[greater_than_t]
我们还可以利用时间戳按时间顺序对数据进行排序。Pandas数据框中的 sort_values 函数提供了多种排序选项,其中之一是按 parsed_time 排序。
sorted_tags = tags.sort_values(by='parsed_time', ascending=True) print(sorted_tags.head(10))
按时间顺序排序时间序列数据有助于改进和实现有效的可视化,因为你可以向可视化提供排序后的数据。
任何带有时间戳的数据都为洞察提供了巨大的可能性,因为当我们知道数据是何时获取的,我们就可以对该时间点有更多的理解。
首先,重新加载包含时间戳的原始 tags.csv 文件到 tags 数据框中。
tags = pd.read_csv('tags.csv') print(tags.dtypes)
timestamp 列的类型是 int64。我们需要将这个整数转换为Python可以呈现为可理解日期的数据类型。
tags['parsed_time'] = pd.to_datetime(tags['timestamp'], unit='s') print(tags[['timestamp', 'parsed_time']].head())
现在,parsed_time 列以人类可读的方式(年-月-日 24小时制格式)显示时间戳。
一旦时间被转换为人类可读的格式,我们就可以使用我们能理解的时间戳来创建过滤器并选择感兴趣的行。
greater_than_t = tags['parsed_time'] > pd.Timestamp('2015-02-01') selected_tags = tags[greater_than_t] print(selected_tags.shape)
我们还可以按时间顺序对行进行排序,使数据从记录的开始时间开始流动,用户可以在这个框架中看到时间顺序。
sorted_tags = tags.sort_values(by='parsed_time', ascending=True) print(sorted_tags.head(10))
一个你可能回答的问题是:电影评分是否与上映年份相关?
我们已经有平均评分数据框 avg_ratings。现在,我们可以将其与包含年份信息的 movies 数据框合并。
# 假设 movies 数据框已包含从 title 提取的 year 列 merged_with_year = movies.merge(avg_ratings, on='movieId', how='inner')
然后,我们可以按年份分组计算每年的平均评分。
yearly_avg = merged_with_year.groupby('year')['rating'].mean() print(yearly_avg.head())
通过绘制每年的平均评分图,我们可以看到某些年份的票房电影看起来更好。
yearly_avg.plot(kind='line', title='Average Movie Ratings Over Time')
通过结合我们所学到的关于过滤、连接、计数、平均和添加列的所有知识,我们能够开始生成洞察,分析哪些年份更适合制作电影,以及它们如何随时间影响我们的评分。
在本节课中,我们一起学习了Pandas库中典型的数据科学操作。我们基于电影数据库笔记本回顾了所学的实践技能:
- 数据摄取:回顾了如何以多种格式摄取数据,以及与这些格式相关的基本读取操作。
- Series和DataFrame:介绍了Pandas中两种基本的数据结构。
- 基本统计操作:概述了对Series和DataFrame执行基本统计操作的函数,包括联合描述性统计以及生成最小值、最大值、标准差、众数等单独函数,还回顾了相关性分析函数。
- 数据准备与探索:介绍了Pandas中的数据准备和探索选项,如
isnull和dropna函数。 - 数据可视化:看到了内联图、箱线图和直方图的示例,以及如何调整图形格式和其他窗口属性。
- 数据切片与过滤:讨论了切出行和过滤数据框,以及使用
groupby操作聚合数据。 - 数据合并:介绍了使用内连接等操作合并或连接来自多个数据框的数据。
- 字符串操作:讨论了三种主要操作:
split、concat和extract。 - 时间戳处理:最后,讨论了如何处理时间戳。

我们构建的电影笔记本是一个具有代表性的示例,可以作为参考,因为任何数据科学研究都会有类似的步骤。
在本节课中,我们将要学习数据可视化的基本概念、它在数据科学中的核心作用,以及如何评估一个可视化作品的质量。数据可视化是数据科学家的一项关键技能,它不仅能帮助我们理解数据,还能有效地传达分析结果。
数据可视化是任何数据科学家的核心技能之一,其价值不容低估。然而,它本身也是一个广阔的领域。在全美各大学和设计实验室,有许多研究生课程专门研究数据可视化。因此,本周的内容只是对这个宏大主题的一个入门介绍。
我们将首先从概念上探讨数据可视化,然后直接使用Python库进行一些可视化实践。最后,我们将审视一些特别有效的数据可视化案例及其影响力。如果你对这个领域感兴趣并希望深入学习,我们也会提供相关教科书和其他资源的链接。
在数据爆炸的时代,正如第一周ILKI所讨论的,处理这些数据的相关技能至关重要。谷歌首席经济学家在2008年曾指出:“获取数据、理解数据、处理数据、从中提取价值、将其可视化并进行沟通的能力,在未来十年将是一项极其重要的技能。”这段引述不仅预示了对数据科学家的需求,也强调了统计学(数据科学的核心领域之一)和理解数据的另一种方式——可视化——的重要性。
事实上,可视化贯穿于他所描述的许多过程。处理数据、从中提取价值或理解数据,通常都需要通过可视化来获得洞察。当你想要传达结果时,也常常需要使用数据可视化。因此,可视化是他所描述技能的核心。
有两个关于可视化的定义我特别欣赏:
- 使用计算机支持的、抽象数据的交互式视觉表示来增强认知。
- 数据的表示和呈现,以促进理解。
这两个定义都强调了表示和呈现数据的重要性,这是数据可视化的核心。但它们最打动我的共同点是,都落脚于数据可视化的关键部分:改善我们理解和思考数据的方式。我们大脑的很大一部分专用于视觉处理,拥有强大的视觉处理能力。相比之下,我们并不擅长解读原始数据。这就是数据可视化的力量。
让我们快速做两个实验。以下所有数据点都来自同一个数据集。你能看出X和Y之间的关系吗?
(此处假设有一组原始数据点列表)
仅看这些数据点可能很困难。再看一些关于X和Y的统计量(如均值、标准差),帮助也不大。相关系数0.88暗示这两个值相关,可能存在线性关系,但我们仍然不完全理解数据。
然而,一旦我们将数据可视化,一切就清晰了。图形显示,Y相对于X呈指数增长,直到X大约为7。此后,Y达到约33的上限,增加X不再增加Y。值得注意的是,相关系数在这里有点误导,它暗示了线性关系,而数据外观并非如此。这个实验的关键在于,人类的视觉皮层非常强大。
让我们再看一组新数据。所有点都来自一个数据集。如果你仔细观察,可能会注意到X和Y值似乎很相似,但带有一些噪声。
同样,让我们通过图形来更好地理解。散点图显示它们确实高度相关:较高的X值意味着较高的Y值,反之亦然。事实上,这里的相关系数统计量会很有帮助,其值为0.5。这个值比你预期的要低,因为存在一个异常值。从视觉上,你可能也注意到了那个奇怪的异常点。
但如果没有上下文,我们不知道该如何处理这个信息或这个异常值。我们甚至不确定是否关心这些数据。所以我们需要上下文。
原来,这些是某个班级每个学生假设的期中考试和期末考试成绩。现在,我们知道期中表现与期末表现密切相关。对于教师来说,这可能导致他们对期中表现不佳的学生进行干预。对于异常值,上下文也有帮助。经过调查,你发现那名学生没有参加期末考试。在数据清理时,缺失的条目被填成了0,但考试得0分和未参加考试是两回事。如果移除这个因数据缺失造成的异常值,你会对数据有更好的理解,并且相关系数会上升到0.89。
这个例子说明,没有数据背景的可视化毫无意义。当我们不知道X和Y代表什么时,我们并不真正关心这些数据。背景使我们重视可视化,并赋予我们深入挖掘数据、解释异常值并对数据本身得出结论的能力。


现在我们有了数据可视化的工作定义,让我们来谈谈在数据科学中使用可视化的不同方式。可视化在数据科学中扮演多种角色,其角色决定了我们如何处理可视化。
有两种关键的方式来对数据可视化进行分类:
- 概念性 vs. 数据驱动性:本周和本课程主要关注数据驱动性可视化。但概念性可视化(旨在解释事物如何运作)也很重要,例如经济学家可视化经典的供需曲线概念。
- 在数据驱动背景下,分为呈现性 vs. 探索性:
- 呈现性:当我们分析完数据,有了数据支持的结论,并希望向观众清晰阐述时,我们希望可视化能以最直接的方式向观察者传达这一结论。
- 探索性:我们花费大量时间探索数据,可视化在其中扮演关键角色。可视化鼓励并使我们能够更深入地审视数据。
以下是来自这些类别的例子:
- 概念性示例:经典的供需曲线。
- 呈现性示例:一项研究中的图表,显示同伴教学法相较于标准教学,显著降低了加州大学圣地亚哥分校计算机科学课程的学生不及格率。目标是向读者清晰展示结果。
- 探索性示例:期中与期末考试成绩相关性的散点图。创建这样的图是为了更好地理解关系,并可能引导进一步的数据探索。
探索性可视化是数据科学过程的核心。当我们寻找异常值或趋势时,经常使用可视化工具。这些可视化引导我们深入数据。在此过程中,我们使用直方图查看数据分布,探索变量间关系或寻找其他趋势。我们不断地在数据集的不同部分“放大”和“缩小”,以更好地理解数据,而这个过程几乎总是由数据可视化伴随和促进的。
现在我们了解了数据驱动可视化的不同用途,接下来探讨衡量这些可视化成功与否的标准。
关于优秀数据可视化的标准有很多观点,但我非常喜欢数据可视化教育者安迪·柯克提出的三点:可信、易懂、优雅。
以下是关于这三点的详细说明:
1. 可信
可信意味着所呈现的数据被诚实地描绘。例如,如果你的展示方式暗示了某种关系、趋势或相关性,那么数据中就应该有证据支持这种关系。否则,你就是在误导观众。
看一个假设的例子:一张可能在商务会议上展示的图表,标题为“第四季度利润激增!”。如果你注意到Y轴,会发现这根本不是激增。Y轴被放大到图表的一小部分,Q1到Q4之间只有大约2%的增长。这很难称得上“激增”。这样的图表作者试图不诚实或至少是误导性的。一个更诚实的图表会诚实地设置Y轴,并可能绘制前几年的利润数据以进行比较。
关键要点:
- 认真对待信任:查看你结果的人信任你不会篡改数据或歪曲结果。他们希望根据你的发现采取行动。
- 诚实贯穿始终:诚实不仅限于可视化阶段,它必须贯穿数据科学的全过程。你需要认识到自己可能存在的偏见,并尽力让数据本身(而非你的偏见)驱动你的探究。
2. 易懂
易懂关乎关注你的观众以及他们使用你可视化的能力。这取决于你的受众是谁。
例如,一张报告计算机处理器“每周期指令数(IPC)”的图表。对于计算机架构专家,这张图可能是诚实且易懂的(取决于用途)。但对于非专业人士,这张图几乎无法理解,没有相关性,甚至可能无意中产生误导。
主要注意事项:
- 了解你的受众:了解他们知道什么,以及他们可能如何解读结果。
- 明确可视化的目的:你是在探索数据还是在呈现结果?这有助于你以适当的方式构建它。
- 考虑受众的理解时间:这取决于这是一张在演示中展示一分钟的幻灯片,还是与可能花时间深入研究的同事分享的图表。
3. 优雅
优雅可以联想到风格、清晰度和美学美感。在实践中,当呈现结果时,我会花更多时间在优雅的可视化上;在探索数据时,优雅是锦上添花,但绝非关键。
可以这样想:
- 如果我的图形要登上《纽约时报》头版,它最好完美无缺。
- 如果用于教学幻灯片,它应该非常扎实。
- 如果是我自己查看数据,它至少应该是可接受的。缺乏优雅不应妨碍我解读数据。
例如,一张显示各国人均二氧化碳排放量的地图覆盖图。其优雅之处在于:在地图上使用覆盖层帮助观察者快速看到不同国家;用颜色编码显示数值数据,帮助观众快速解读结果(尽管颜色方案可能不是最理想的);没有不必要的其他数据;任何额外的装饰(如在高排放国图标上加烟囱)都应增色而非减分。
对于优雅的可视化,你应该专注于相关的内容,并移除任何没有为图形增添价值的东西。你试图让设计“隐形”,以便观众能在不受干扰的情况下尽可能多地获取可视化信息。这不同于有些人主张的极简主义,需要在包含多少内容之间取得平衡。同时,要考虑你的风格。装饰有时可能与诚实原则相悖,但了解你的目标受众是关键。

在本节课中,我们一起学习了数据可视化的核心概念。我们定义了数据可视化,并理解了它在理解、探索和传达数据洞察方面的关键作用。我们探讨了数据可视化的主要分类:概念性与数据驱动性,以及呈现性与探索性,并了解了它们在不同数据科学场景中的应用。最后,我们介绍了评估数据可视化质量的三个关键原则:可信、易懂和优雅。记住这些原则,将帮助你在未来创建出既有效又负责任的可视化作品。
在本节课中,我们将学习如何使用Python的Matplotlib库进行数据可视化。我们将从基础概念讲起,逐步学习如何创建条形图、折线图、散点图和直方图,并探索如何利用世界发展指标数据集进行实际分析。课程最后,我们还将简要介绍其他高级可视化库,如Folium,用于创建地理覆盖图。
Matplotlib是一个广泛使用的Python绘图库。它是在数据探索过程中快速创建数据图的常用工具。上一课中展示的几乎所有图表都是使用Matplotlib制作的。
Matplotlib的价值在于,在进行数据探索时,它能轻松快速地生成数据图,并且其生成的图表通常简洁美观。
Matplotlib主页上的一句话很好地概括了其特点:“让简单的事情变得简单”。这正是它在数据探索中成为首选工具的原因。只需几行代码,您就可以生成条形图、折线图、散点图、箱线图或直方图。这非常棒,因为您可以快速检查特征之间的关系、绘制趋势线,或者更好地了解数据分布。
“让困难的事情成为可能”则是您即使在创建复杂可视化或为展示结果定制图表时,也常常会使用Matplotlib的另一个原因。
一个关于Matplotlib的有趣故事是,我的一位同事曾花时间为我们在期刊上发表的一篇论文中的图表设置了脚本。由于他希望设计优雅,并且希望字体与论文文本无缝融合,这个设置花费了一些时间。如果您读过研究论文,您无疑会看到过与论文格格不入的图表。图表显得突兀的一个原因是所有字体完全不同。另一个原因是,如果您在论文中使用LaTeX,图表可能看起来不如论文优雅。因此,他的工作旨在解决这些常见问题。
他工作的伟大之处在于,由于他依赖Python脚本、读取CSV文件和Matplotlib,我们使用makefile将这些脚本集成到论文的构建过程中。因此,当我们用一些新结果更新CSV文件后,只需点击构建,就能获得带有这些非常清晰图表的更新论文。这一切都得益于Matplotlib。
在Jupyter笔记本和Python工具的大背景下,您可能知道Matplotlib是一个相当底层的绘图工具。市面上还有许多其他工具,包括Seaborn、ggplot、Altair、Bokeh、Plotly和Folium等。对我们来说,我们会根据可视化需求选择最适合的库。因此,在本课稍后部分,我们将提供一份阅读材料,概述何时可能想使用某个库而不是另一个,并且我们会随着新库的出现或库添加新功能而不断更新这份材料。
鉴于有这么多库可用,并且我们在日常工作中经常使用Matplotlib,我们将首先深入一个展示Matplotlib的笔记本。然后,稍后我们会有一个简短的笔记本,向您展示如何使用更高级的库。
在深入可视化之前,请记住,如果没有上下文,可视化几乎没有意义。因此,本周我们将开始使用世界发展指标数据集,这是Kaggle上的一个开放数据集。这只是世界银行实际可用数据集的略微修改版本。
到本视频结束时,您应该能够使用世界发展指标数据集进行数据科学分析。
处理任何数据集的第一步是进行初步探索。首先,让我们导入pandas、numpy、random和matplotlib.pyplot。
import pandas as pd import numpy as np import random import matplotlib.pyplot as plt
接下来,将CSV文件读入pandas DataFrame并显示数据的形状。为了让代码对您有效,请确保您已下载数据并将其放在适当的目录中。
data = pd.read_csv('world_development_indicators.csv') print(data.shape)
这是一个大数据集,读取可能需要一些时间。实际上,该数据集有560万行和6列。现在,让我们使用head方法查看这些列包含什么内容。
print(data.head())
有趣。我们有国家名称、国家代码、指标名称、指标代码、年份和值。这实际上是一个四维数据集,维度分别是国家、指标、年份和值。查看这些指标,我已经看到一些非常有趣的东西。作为一个有环保意识的人,我对人均二氧化碳排放量相当好奇,我们稍后会用到这个指标。
我还想知道这个数据中有多少个国家。我可以通过对数据框的列使用unique方法来测试,以找出该列中有多少个唯一条目。
print(data['CountryName'].nunique())
看起来我们大约有247个国家。我想对数据做一个快速的完整性检查:如果我们有247个国家,我们应该有247个国家代码。确实如此。我还想知道我们有多少个指标。
print(data['IndicatorName'].nunique())
1344个指标是一个非常广泛的指标列表。如果您想探索指标本身的完整列表及其更多详细信息,我们在本笔记本顶部提供了一个链接。
好了,现在我们知道了有多少个国家和多少个指标,接下来我们需要知道我们有多少年的数据。
print(data['Year'].nunique()) print(data['Year'].min(), data['Year'].max())
56年对于数据收集来说是一个相当不错的时间范围。让我们看看这个时间范围是什么:1960年到2015年,很好。至此,我们对数据集有了相当好的了解。我们拥有1960年至2015年期间每个国家的各种指标。在下一个视频中,我们将继续使用Matplotlib中的可视化来探索数据。


上一节我们介绍了数据集的基本情况,本节中我们来看看如何使用Matplotlib创建基础图表。
回到我们上一个视频中的Jupyter笔记本,我们已经对世界发展指标数据集中包含的内容有所了解。现在我想做的是探索美国的人均二氧化碳排放量。为此,我将使用上周介绍的字符串方法contains设置两个掩码。
第一个掩码用于所有指标名称包含“CO2 emissions”的行。第二个掩码是那些国家代码为“USA”的行。我将把该数据的结果保存在一个名为stage的临时数据框中。
让我们检查一下新数据框中有什么。在我们的新数据框中,所有国家都是美国,国家代码是US,指标是二氧化碳排放量。看起来我们拥有美国按年份划分的人均二氧化碳排放量数据。
现在,让我们使用Matplotlib探索这些数据如何随时间变化。我可以用两行代码完成这个操作:我将分别获取年份和二氧化碳排放量,然后将它们传递给bar函数。还记得我在上一课中说过,只需几行Matplotlib代码就能让我为数据探索创建一个图表吗?这就是一个完美的例子。
# 筛选美国人均CO2排放数据 co2_mask = data['IndicatorName'].str.contains('CO2 emissions') usa_mask = data['CountryCode'] == 'USA' stage = data[co2_mask & usa_mask] # 提取年份和值 years = stage['Year'] co2_emissions = stage['Value'] # 创建条形图 plt.bar(years, co2_emissions) plt.show()
我可以看到,人均二氧化碳排放量从1960年到1970年有所上升,此后基本保持稳定。这个图表目前并不完美,我没有Y轴标签,这在这里实际上非常重要。如果不知道我绘制了什么,这个图表本身很难独立理解,但同样,如果我只是想探索数据,这也没关系。
为了让这个图形更具吸引力,让我们使用折线图。我将使用plt.plot,添加X轴和Y轴的标签,并添加标题。我不确定是否需要X轴标签,很明显这些是年份,但为了清晰起见,我们还是加上它。
plt.plot(years, co2_emissions) plt.xlabel('Year') plt.ylabel('CO2 emissions (metric tons per capita)') plt.title('USA CO2 Emissions per Capita Over Time') plt.show()
更仔细地看一下这几行代码。首先,plt.plot方法用于创建折线图。在图表本身中标记坐标轴称为xlabel和ylabel,这些是绘图对象上的方法。这些函数中的每一个都接受许多参数,您可以使用这些参数更改字体大小、颜色等。同样,我们设置了标题,然后绘制了图表。
这很好,但请注意Y轴实际上是从15开始的,正如我们从之前的课程中学到的,这可能会产生误导。因此,让我们通过调用axis并传递我们想要绘制的范围来修复这个问题。
plt.plot(years, co2_emissions) plt.xlabel('Year') plt.ylabel('CO2 emissions (metric tons per capita)') plt.title('USA CO2 Emissions per Capita Over Time') plt.axis([1960, 2015, 0, 25]) # 设置X轴和Y轴范围 plt.show()
现在的图表可以说比原始条形图更好,并且由于Y轴标签正确,它当然更能独立存在。
接下来,让我们使用直方图来探索数据。我将绘制数据中所有的人均二氧化碳排放量值,但我在注释中放了一些代码,可以让您只探索一个标准差范围内的值。有时对于直方图来说,避免数据因为异常值而过于分散是有帮助的,但我们现在先绘制所有数据。
# 获取所有CO2排放值(NumPy数组) hist_data = stage['Value'].values print(len(hist_data)) # 数据点数量 # 创建直方图 plt.hist(hist_data, bins=10, normed=False, color='green') plt.xlabel('CO2 emissions (metric tons per capita)') plt.ylabel('Number of Years') plt.title('Distribution of USA CO2 Emissions per Capita (1960-2015)') plt.grid(True) plt.show()
我们得到了一个对美国人均二氧化碳排放量进行分箱的直方图。这告诉我大多数年份落在18.5到20之间,有一些异常值。如果您觉得难以阅读,我们可以添加网格线。所以让我这样做:如果我设置plt.grid(True)然后重新运行,对我来说更容易获取年份的计数。
鉴于我们通常落在人均19到20公吨之间,我很好奇美国相对于其他国家的表现如何。所以让我们选择一个我有美国数据的最近年份:2011年。
我将查询指标为“CO2 emissions per capita”且年份为2011年的数据。这应该会给我提供在该时间窗口内向我们提供数据的所有国家。
# 获取2011年所有国家的人均CO2排放数据 co2_2011 = data[(data['IndicatorName'].str.contains('CO2 emissions per capita')) & (data['Year'] == 2011)] print(co2_2011.head()) print(len(co2_2011))
很好,看起来我得到了我们想要的东西。我们有不同的国家及其2011年的人均二氧化碳排放量。仅仅看这些数值,4.7, 6.9, 5.8, 5.3,我已经怀疑美国可能比其他国家产生更多的人均二氧化碳。事实上,在我们开始这个分析之前,我可能已经猜到了,但让我们用直方图来看看这是如何分布的。
我们这里有多少个国家?我们有232个。这包括美国,因为我没有做任何特殊的事情来排除美国。
现在让我们创建图表。请注意,我在开头做了一些稍微不同的事情:调用plt.subplots会分别返回图形和坐标轴对象。我将使用坐标轴引用进行注释,但在我们查看注释之前,让我们看看plt.hist调用,并看到我们设置的基本上与上一个直方图相同,只是这次我们计算的是具有特定人均排放量的国家数量,而不是年份数量。
fig, ax = plt.subplots() values_2011 = co2_2011['Value'].values ax.hist(values_2011, bins=20, color='blue', edgecolor='black') ax.set_xlabel('CO2 emissions (metric tons per capita)') ax.set_ylabel('Number of Countries') ax.set_title('Global Distribution of CO2 Emissions per Capita (2011)') # 添加美国数据的注释 usa_value = co2_2011[co2_2011['CountryCode'] == 'USA']['Value'].values[0] ax.annotate('USA', xy=(usa_value, 5), xytext=(usa_value+1, 15), arrowprops=dict(facecolor='red', shrink=0.05)) plt.show()
哇,看起来绝大多数国家的人均二氧化碳排放量在0到10公吨之间。美国在2011年约为17,实际上是一个真正的异常值。但是那个漂亮的“USA”标签是从哪里来的?这是我们在Matplotlib中讨论过的功能之一,它支持在图表上执行更复杂的操作,如添加注释或线条。
如果我们回到代码,我们会注意到我添加了一个字符串“USA”的注释。我将其放置在坐标(18, 30)处,然后从坐标(18, 30)到(18, 5)画了一条线。如果您希望阅读更多文档,annotate方法还有更多参数。但主要是,我想向您展示这一点,以指出(抱歉用了双关语)您可以使用Matplotlib进行更高级的图形绘制。
关于这些数据,我还有更多问题,但如果您愿意,我将留给您去探索。但在结束视频之前,我想指出,在仅仅看了这几个图表之后,您可能已经发现了Matplotlib图表的共同特征。让我们明确地陈述它们。
以下是Matplotlib图表的常见组成部分:
- 图表类型:例如,我们这里有条形图、折线图、直方图。
- X和Y值的范围:通过
axis或xlim/ylim设置。 - 坐标轴标签:使用
xlabel和ylabel。 - 图表标题:使用
title。 - 图例:当有多个数据系列时使用
legend。 - 美学设置:如字体大小、线条粗细、图表尺寸,甚至更复杂的东西如注释。
现在我们已经了解了Matplotlib绘图的这些功能,我鼓励您使用这个笔记本以及我们本周提供的其他笔记本来获取更多示例。但在我们结束Matplotlib之前,我想探索二氧化碳排放量与GDP之间的关系,但让我们在下一个视频中做这件事。
上一节我们学习了基础图表的绘制,本节中我们来看看如何使用折线图和散点图探索数据之间的关系。
我们将继续在这个视频中使用Matplotlib,继续探索我们开始时使用的世界银行数据集。到本视频结束时,您应该能够使用Matplotlib创建折线图和散点图。
关于数据的一个问题将允许我们使用折线图和散点图进行探索。我们看到美国的人均二氧化碳排放量在20世纪60年代上升,然后除了一些微小的变化外,此后基本保持稳定。我很好奇这与美国经济,特别是美国的人均GDP有何关系。
因此,让我们设置与上一个视频类似的掩码,以提取美国随时间变化的人均GDP。
# 筛选美国人均GDP数据 gdp_mask = data['IndicatorName'].str.contains('GDP per capita') usa_gdp = data[gdp_mask & usa_mask] print(usa_gdp.head())
看起来我们拥有我们想要的数据:每年我们都有基于2005年美元价值的人均GDP。这与我们的人均二氧化碳排放量数据非常相似。
好了,在将它们相互绘制之前,让我们先看看美国人均GDP的趋势。我将为此使用折线图。
gdp_years = usa_gdp['Year'] gdp_values = usa_gdp['Value'] plt.plot(gdp_years, gdp_values) plt.xlabel('Year') plt.ylabel('GDP per capita (constant 2005 US$)') plt.title('USA GDP per Capita Over Time') plt.grid(True) plt.show()
在大多数情况下,我们看到随着时间的推移有稳定的增长。这里有一些下跌,2008年经济衰退期间有一次明显的下跌,但到2010年上升趋势就恢复了。因此,知道在同一时间段内二氧化碳排放量没有以相同的方式变化,使我认为它们之间没有密切关系。
但让我们看一个散点图来确认。首先,我想通过调用这些列的min和max来确保时间范围相同,我想确保它们相同的原因是散点图要求数据集中有相同数量的年份数据。
print("CO2 years range:", stage['Year'].min(), stage['Year'].max()) print("GDP years range:", usa_gdp['Year'].min(), usa_gdp['Year'].max())
它们都从1960年开始,但我拥有的GDP数据比CO2数据多。因此,我需要为散点图准备相同数量的数据点,为此,我将修剪掉那些额外的数据。要进行这种修剪,我将要求年份在2012年之前,然后检查我们的数据在GDP和二氧化碳排放量方面是否具有相同数量的值。
# 确保数据年份对齐(例如,都截至2011年) stage_trimmed = stage[stage['Year'] <= 2011] usa_gdp_trimmed = usa_gdp[usa_gdp['Year'] <= 2011] print(len(stage_trimmed), len(usa_gdp_trimmed))
好了。我们两个都有52年的数据。要制作散点图,我们首先像之前一样使用plt.subplots调用图形和坐标轴。其余大部分应该相当容易识别,除了对scatter方法的调用,它用于用这两个数组创建散点图。
fig, ax = plt.subplots() ax.scatter(usa_gdp_trimmed['Value'], stage_trimmed['Value']) ax.set_xlabel('GDP per capita (constant 2005 US$)') ax.set_ylabel('CO2 emissions (metric tons per capita)') ax.set_title('USA: CO2 Emissions vs. GDP per Capita (1960-2011)') plt.grid(True) plt.show()
现在当我运行这个时,我得到的是一个相当弱的关系。看起来当GDP和二氧化碳排放量在60年代上升时,它们是一起变化的。但此后,似乎根本没有太大关系。
我们也可以使用相关性来测试这一点。我将使用numpy中的相关系数函数来获取这两个数组之间的关系。
correlation_matrix = np.corrcoef(usa_gdp_trimmed['Value'], stage_trimmed['Value']) print(correlation_matrix)
主对角线是每个数组与自身的对比,因此我们期望在那里看到1.0或完全相关。但在另一条对角线上,我们看到0.077。这是这两个指标之间非常弱的相关性。
因此,我们刚刚开始研究这些关系,但如果有人争辩说我们需要更多的人均二氧化碳产量来提振经济,这个初步的数据分析并不支持这一说法。
关于如何处理这些数据,我还有更多想法:我们能否看看其他国家的人均二氧化碳排放量与GDP之间的关系?或者看看整体二氧化碳排放量和GDP(去掉人均部分)?这是一个极其丰富的数据集,因此我鼓励您继续自行探索。
上一节我们探索了数据间的关系,本节中我们来看看更高级的图表类型以及如何使用其他库(如Folium)进行地理可视化。
这是一个简短的视频介绍,介绍了我们本周提供的包含额外Matplotlib示例的笔记本。到本视频结束时,您应该能够使用额外的Jupyter笔记本资源作为示例。
在我们的附加笔记本中,我们提供了许多示例的代码,仍然使用世界发展指标数据。我们随机挑选指标,并使用折线图和散点图将它们相互比较。
在处理完世界发展指标之后,我们还提供了一个如何在Matplotlib中创建3D绘图的示例。我们还提供了一个使用气泡图的示例。当您想轻松绘制三个维度时,气泡图会非常有帮助。您可以拥有X、Y和每个点的大小。在这个例子中,我们还使用颜色编码来提供第四个维度,所以在这个图像中,我们有角度、距中心的距离、气泡大小和颜色都编码在这里。
请注意,与我们早期的一些图形不同,您需要花时间解释和理解像这样的图形中的数据,但对于数据探索以及在呈现数据时传达更复杂的关系,它可能非常有价值。
除了直方图,我在试图理解分布时经常使用箱线图。这个示例为您提供了箱线图的模板,以及如何在Matplotlib中并排放置图形。箱线图告诉您四分位距内的中值、高于第三四分位数和低于第一四分位数的元素,以及最大值和最小值。这是一个图形中包含的大量有用信息。
因此,当您在使用Matplotlib和即将到来的项目周中寻找可视化数据的方法时,请务必查看这些额外的笔记本以及网上的其他示例。
我们之前提到,除了Matplotlib之外,还有许多有用的库,特别是在处理像世界发展指标这样的数据集时,创建地理覆盖图可能是可视化数据的强大方式。
到本视频结束时,您应该能够使用Folium库创建地理覆盖图。
跳转到我们的Jupyter笔记本。在您能够使用Folium之前,您可能需要在系统中安装它。您还需要获取下面列出的JSON文件,并将其放在您的文件夹中,路径为geojson/countries.json(如果尚未这样做)。
让我们进行设置。导入folium和pandas,然后设置地理数据。
import folium import pandas as pd # 读取世界发展指标数据(可能需要时间) data = pd.read_csv('world_development_indicators.csv') print(data.head())
接下来我们想做的是,像之前一样,提取2011年所有国家的二氧化碳排放量。
co2_2011 = data[(data['IndicatorName'].str.contains('CO2 emissions per capita')) & (data['Year'] == 2011)]
检查数据,看起来我们得到了我们想要的指标。让我们通过仅保留国家代码和我们绘制的值来设置我们的绘图数据。我们还将提取指标名称以用作图形中的图例。
plot_data = co2_2011[['CountryCode', 'Value']] indicator_name = co2_2011['IndicatorName'].iloc[0]
好了,现在我们实际上已经准备好创建Folium交互式地图了。我们将告诉它在一个相当高的缩放级别创建地图。接下来,我们将使用内置方法choropleth来附加国家的地理JSON和绘图数据。我们需要指定相关的列。key_on='feature.id'指的是JSON对象中的标签,该标签将国家代码作为feature.id附加到每个国家的边界信息上。您可以通过读取JSON对象找到这一点。但这是我们建立数据关联所需的:数据框中的国家代码与JSON对象中的feature.id匹配。
接下来,我们指定一些美学设置,如配色方案和不透明度,然后标记图例。因此,此绘图的输出将保存为HTML文件,并且该HTML文件实际上是交互式的。所以我们需要做的是保存它,然后将其读回笔记本,以便在地图上进行交互。
# 创建基础地图
world_map = folium.Map(location=[30, 0], zoom_start=2)
# 添加地理覆盖层
world_map.choropleth(
geo_data='geojson/countries.json',
data=plot_data,
columns=['CountryCode', 'Value'],
key_on='feature.id',
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
legend_name=indicator_name
)
# 保存为HTML文件并显示
world_map.save('co2_emissions_2011.html')
# 在Jupyter Notebook中显示
world_map
然后我们得到了我们的地图。首先注意,深色意味着更高的人均二氧化碳排放量。美国和一些欧洲国家以及中东国家作为人均二氧化碳高排放国脱颖而出。但请记住,这不是二氧化碳总排放量,而是人均二氧化碳排放量,因此人口众多的国家可能二氧化碳总排放量很高,但人均二氧化碳排放量仍然较低。
因此,我们提供这个笔记本作为如何进行地理覆盖的示例,也作为如何使用额外可视化库的示例,以及它们如何根据您的可视化需求而变得强大。因此,请务必查看资源以及我们关于哪种可视化库最适合您需求的库推荐。

本节课中我们一起学习了如何使用Matplotlib进行数据可视化。我们从Matplotlib的基础介绍和世界发展指标数据集的准备开始,逐步学习了创建条形图、折线图、直方图和散点图的方法。我们还探索了如何利用这些图表分析数据间的关系,例如美国人均CO2排放与GDP的相关性。最后,我们简要介绍了高级图表(如3D图、气泡图、箱线图)和地理可视化库Folium的使用。Matplotlib作为核心绘图工具,其“易事易为,难事可为”的理念使其成为数据探索和结果展示的得力助手。
在本节课中,我们将通过两个历史案例,学习数据可视化如何帮助人们理解复杂问题并推动科学认知。我们将首先探讨约翰·斯诺如何利用地图可视化揭示了霍乱的真正传播途径,然后分析查尔斯·米纳绘制的拿破仑远征俄罗斯的经典信息图。
上一节我们介绍了课程目标,本节中我们来看看第一个经典案例:约翰·斯诺的霍乱地图。这个可视化帮助世界理解了霍乱的真正成因。
霍乱是一种细菌感染,可导致严重腹泻,可能因脱水而致命。接触细菌后,患者会在12小时至5天内出现症状,急性病例若未经治疗可在数小时内死亡。不幸的是,霍乱至今仍构成威胁,全球每年有数百万病例,死亡人数达数万至数十万。
霍乱在卫生条件差的地区尤其危险,极易导致疫情爆发。霍乱患者的腹泻物中含有细菌,感染后可持续排菌长达10天。若有人摄入这些细菌,就会感染疾病。因此,如果污水污染了食物或水源,就可能引发疫情。
今天,我们之所以了解霍乱的传播方式,得益于一位科学家的理论和1854年在伦敦发生的一次严重疫情。
在19世纪的伦敦,卫生状况堪忧,尤其是在贫困社区。随着人口增长以及缺乏适当的污水和卫生管理规范,许多人的生活条件极其恶劣。他们通常从水井取水,但井水易受污染。
当时的人们并不知道霍乱通过水传播。那么,他们认为是什么导致了霍乱呢?他们实际上认为是“瘴气”(难闻的气味)和“体质虚弱”共同作用的结果。
第一种理论导致了医生佩戴口罩,如下图中的形象。这幅艺术作品实际上与医生试图避免黑死病有关,但同样的理论在19世纪仍然存在。人们认为难闻的气味会导致疾病,如果在口罩里放些香料,就能避免闻到这些气味。尽管我们现在知道这种想法是错误的,因为我们有了细菌理论。我猜想在过去,这种观念的起源很可能只是错误地将相关性当成了因果关系。在有人生病或卫生条件差的地区,往往气味也更难闻。由于大多数人在气味难闻的地区生病,他们便错误地将气味归咎为疾病的源头。
第二种观念在当时更令人反感。穷人往往更容易患病。考虑到我们现在对疾病传播方式以及当时他们生活条件的了解,这并不奇怪。但在当时,一些人认为穷人只是天生更虚弱,因其自身的弱点而容易患病。这同样是错误地将相关性当成了因果关系,但这次还夹杂着偏见。
现在你对他们认为的疾病成因有了基本了解,这引出了约翰·斯诺。他是19世纪生活在伦敦的一位科学家,背景是麻醉师,曾运用科学原理使麻醉实践对病人安全得多。他还在1849年发表了他的理论,认为霍乱是水媒传播的,但他的理论几乎没有引起关注。
1854年,约翰·斯诺住在伦敦,靠近霍乱疫情爆发的地方。这次疫情是由受污染的水进入宽街水泵的水井引起的。疫情爆发后,约翰·斯诺在疫情附近区域挨家挨户走访,追踪每一个死亡病例,以查明他们的住址和取水地点。他将所有这些死亡病例绘制在一张地图上。
😊,这是完整的地图。有点难以看清,但宽街水泵正好位于所有那些街区的中心。每一个小矩形块代表一例霍乱死亡。如果我们放大,可以看到水泵周围的社区有大量死亡病例,而远离水泵的地方,死亡人数减少。
通过与死者家属交谈,他得以了解受害者从哪里取水。在一个完美的数据科学案例中,异常值对于找到令人信服的答案至关重要。
宽街水泵附近有一个济贫院(或监狱),里面有许多囚犯,但几乎没有死亡病例。结果发现,济贫院有自己的水井,并从不同水源运水,因此他们没有使用那个水泵。附近还有一个啤酒厂,也逃脱了霍乱。同样,他们有自己的供水,或者喝厂里生产的酒,因此他们也不从那个水泵取水。
最后,有一位妇女和她的侄女住在离水泵较远的地方,却死于霍乱。经过一些调查,他发现这位阿姨以前住在该地区,非常喜欢宽街水泵的水,以至于让人把水送给她,并经常与她的侄女分享。
这张图与约翰·斯诺收集的数据相结合,促使城镇官员关闭了那口水井,移除了水泵手柄。尽管官员们对水泵是病源持怀疑态度,但新的感染病例几乎立即减少并很快停止了。
因此,尽管通过移除水泵手柄的行动遏制了这次疫情,但约翰·斯诺的观点被广泛接受还是花了一些时间。这个谜题的一个关键线索后来由该地区一位名叫亨利·怀特黑德的牧师提供。他曾试图证明约翰·斯诺是错的,但在这个过程中,他偶然发现了水泵如何被污染的谜底。疫情爆发前不久,水泵附近一所房子里有一个婴儿感染了霍乱。父母将婴儿的尿布清洗在一个靠近水井的污水坑里,污水随后渗入水井,造成了污染。
另一个与霍乱相关的关键进展发生在几年后,德国医生罗伯特·科赫在1883年分离出了导致霍乱的细菌。这些疫情及其成因的认识,促使欧洲和美国采用了水卫生处理措施。斯诺因其工作,常被视为流行病学领域的先驱之一。
需要指出的是,尽管世界上许多地方都能获得清洁用水,但霍乱的故事可悲地并未结束,正如我在本视频开头告诉你的数字。世界卫生组织估计,世界上近80%的人缺乏清洁用水供应,而且在许多这样的地区,也没有污水处理设施。尽管我们知道如何预防霍乱,它仍然是一个全球性的健康问题。
为了以积极的基调结束,我想分享一张我2013年在伦敦宽街水泵旁拍摄的照片。我当时在那里参加会议,并且非常欣赏围绕宽街水泵的科学、数据可视化及其背后的故事,这实际上是我那次旅行的第一站之一。😊。

我还想最后快速推荐一下《幽灵地图》这本书,副标题是“伦敦最恐怖疫情的故事,以及它如何改变了科学、城市和现代世界”,作者是史蒂芬·约翰逊。我对这个故事的大部分了解都来自阅读这本书。当我在斯基德莫尔学院教授一门关于科学素养和疾病的跨学科课程时,我们将这本书作为必读材料。因此,我实在无法更推荐它了。


上一节我们学习了约翰·斯诺利用地图对抗霍乱的经典案例,本节中我们来看看另一个著名的可视化作品,它被用来描绘1812年法国的俄罗斯战役。
在本视频结束时,你应该能够解释为什么拿破仑1812年俄罗斯战役的数据可视化被认为如此有效。

查尔斯·约瑟夫·米纳于1869年创作了这幅图。图的标题翻译成英文是“1812-1813年法国军队在俄罗斯战役中连续兵力损失的象征性地图”。米纳是法国一位成功的土木工程师,被认为是工程和统计学中使用可视化的早期领导者。他最著名的就是创作了这幅描绘俄罗斯战役的图。
在图中,前进中的法军用浅色线条表示,从渡过涅曼河向莫斯科进军。撤退中的法军用黑色线条表示。线条的宽度描绘了军队的规模。
你可以快速放大到涅曼河,在那里找到战役开始和结束时军队的规模。你几乎可以立刻看出这次入侵俄罗斯对法国来说是多么灾难性。他们开始时拥有大约45万军队(尽管有些估计数字实际上更高),而结束时仅剩不到1万人。
那么,这些损失是如何发生的呢?让我们利用这幅图简要回顾一下这场战役。
首先,这一切是如何开始的?1812年,在沙皇亚历山大一世统治下的俄罗斯,作为法国的盟友,正在封锁英国货物,以遵守拿破仑禁止与英格兰贸易的法令。然而,与英格兰的贸易禁令对俄罗斯经济造成了灾难性影响,因此俄罗斯决定终止参与禁运。拿破仑在与俄罗斯谈判恢复禁运失败后,于1812年开始了他入侵俄罗斯的战役。
计划是在俄罗斯边境附近攻击规模较小的俄军,在战斗中将其击溃,从而结束战争。这是计划。然而,俄军拒绝与拿破仑的大军进行直接战斗。随着俄军向莫斯科撤退,他们会摧毁沿途的乡村,导致法军补给短缺。你可以在图中这个高亮区域看到这一点。此外,俄罗斯哥萨克会对法军进行小规模的袭扰攻击,既造成人员伤亡,也降低了士气。在没有进行任何战斗的情况下,法军遭受了巨大损失,主要是由于疾病和逃兵。
拿破仑终于在莫斯科郊外的博罗季诺村得到了他渴望的战斗。战斗非常残酷,最终法国取得了胜利,但并未取得其期望的决定性胜利。9月14日,法军进入莫斯科,却发现城市已被遗弃,不久后,大部分城区陷入火海。拿破仑在进入莫斯科时期待着俄罗斯投降,但他永远没有等到。在莫斯科等待谈判投降一个月后,开始飘起雪花。拿破仑意识到他的军队无法在莫斯科过冬。
离开莫斯科后,法军遭到俄军攻击,俄军此时已得到增援,实际上能够发起进攻了。他们的存在迫使拿破仑选择一条更北的路线逃跑,而随着冬季持续降临,这带来了问题。此外,俄军能够在法军继续逃跑时不断攻击他们。
撤退期间的条件极其恶劣。部队缺乏补给,在逃跑时不断遭到攻击,并且必须忍受提前到来且严酷的冬季。在这幅图上,还标注了部队在撤退期间面临的温度。温度常常低于零度,军队遭遇了大量降雪。
因此,当拿破仑的军队返回起点时,其规模已只是原先的一小部分。失去了昔日大军的威力,拿破仑在欧洲的权力开始受到挑战。
那么,是什么让这个可视化如此特别?最值得注意的是,它在一个图形中可视化了六种类型的数据,我们已讨论过。
以下是图中编码的数据类型:
- 军队的纬度和经度(地理位置)。
- 军队的行进方向(前进与撤退)。
- 军队的规模(线条宽度)。
- 军队行进的距离(路径长度)。
- 军队在撤退期间面临的温度(底部折线图)。
- 关键的时间点与事件(文字标注)。
通过编码所有这些类型的数据,一幅图就能够完整呈现1812年俄罗斯战役中法国损失的故事。
在本节课中,我们一起学习了两个历史上经典的数据可视化案例。
首先,我们回顾了约翰·斯诺的霍乱地图。通过将死亡病例精确地标注在地图上,并与水源信息结合,斯诺成功证明了霍乱是通过受污染的水井传播的,而非当时主流认为的“瘴气”。这个案例展示了如何通过空间数据的可视化发现相关性,进而推断因果关系,并强调了关注数据异常值的重要性。
接着,我们分析了查尔斯·米纳的拿破仑俄罗斯远征图。这幅杰作在一个视图内融合了军队规模、行进路径、方向、距离、温度和时间等多种维度的数据,生动而深刻地揭示了这场军事行动的灾难性后果。它展示了多变量数据可视化的强大叙事能力,能够将复杂的历史事件浓缩为一目了然的图形故事。

这两个案例共同表明,优秀的数据可视化不仅是展示数字的工具,更是发现真理、讲述故事和影响决策的强大手段。它们跨越时代,至今仍是数据科学和可视化领域的学习典范。

在本节课中,我们将把之前学到的所有数据科学工具和流程整合起来,完成一个小型项目。你将选择一个数据集,提出研究问题,进行探索性分析,并最终提交你的工作成果进行同行评审。
本周的核心任务是综合运用你迄今为止学到的所有知识,完成一个实践项目。
到目前为止,你已经学习了数据科学流程、Jupyter Notebook、NumPy、Pandas以及Matplotlib数据可视化。我们也共同使用这些工具分析了WiFi数据、电影评分数据库和世界发展指标数据等。
你本周的目标是亲自处理其中一个数据集,并基于我们已有的发现进行扩展和深化。
以下是完成本项目需要遵循的具体步骤。
- 选择数据集:从我们提供的Notebook中已探索过的数据集中选择一个。
- 提出研究问题:针对所选数据集,构思一个合适的研究问题。
- 进行初步探索:对数据进行初步的探索性分析。如果能得出一些结论会很好,但务必诚实地呈现你的发现。
- 回答问题并提交:完成后,你需要回答几个关于你所尝试解决的问题的简短问题,然后提交你的Notebook。
- 注重成果展示:请务必在你的作品呈现上多下功夫,清晰地与他人沟通你的发现至关重要。
你将提交上述成果进行同行评审。评审不仅会根据你的工作质量进行评分,你还会收到关于未来如何改进的建设性反馈。
上一节我们介绍了项目提交要求,本节中我们来看看如何进行有效的同行评审。请认真对待你作为评审者的角色。
你需要投入合理的时间来评审他人的工作。在此过程中,请意识到你代表了我们课程团队,因此请以你希望被对待的方式来评审你的同学。
作为一名科学家,我既以论文提交者身份,也以评审者身份参与过同行评审。对于那些尚未参与过此过程的学习者,一个好的评审应该是经过深思熟虑的,并能就如何改进提供建设性反馈,而不仅仅是赞扬。一个糟糕的评审则可能过于苛刻、批评过度、对工作的理解有误,或者过于肤浅而无法提供有价值的反馈。
请以同样的方式对待你的同行评审:你应该理解他们所做的工作,找出优点和不足,赞扬优点,并对不足之处提出建设性的建议。
请以有意义的方式权衡优缺点。不要仅仅因为笔记中的一个拼写错误就判定他人不及格,也不要因为一个存在明显缺陷的肤浅分析就给予满分。
最后,请保持公平、尊重和专业。如果你不确定在与他人互动时期望的行为准则,请查阅edX的行为准则。
轻松一点说,确实有人可能和你的想法不同。也许你是一个狂热的《哈利·波特》粉丝,不喜欢电影数据库分析显示《指环王》电影比《哈利·波特》更好。请尽量公平地评审他们的工作,即使你不喜欢他们的结论。
你已经走过了很长的数据科学和Python学习之路,这是一个将你的技能付诸实践的机会。

希望你能在一个自己感兴趣的数据集上进行这次实践,所以请尝试享受这个过程。

在本节课中,我们一起学习了如何规划并执行一个小型数据科学项目,从选择数据集、提出问题到进行分析和展示。同时,我们也深入了解了进行公平、建设性同行评审的重要原则。现在,是时候将所学知识整合起来,开始你的探索之旅了。

在本节课中,我们将学习机器学习的基本概念,了解其定义、主要应用领域以及核心的分类方法。我们还将介绍机器学习中常用的术语,并概览一个强大的Python机器学习库。
机器学习是计算机科学的一个研究领域,专注于构建能够从数据中学习的计算机系统。这些系统,通常被称为模型,可以通过分析特定问题的大量示例来学习执行特定任务。
例如,一个机器学习模型可以通过查看大量猫的图片来学习识别猫的图像。这种从数据中学习的概念意味着机器学习模型可以自行学习特定任务。机器学习算法被编程为从数据中学习,但算法或程序中并没有直接旨在学习给定任务的逐步指令。相反,模型从分析的数据中自行学习哪些特征对于确定图片是否包含猫是重要的。
因为模型从数据中学习执行任务,所以需要注意,用于构建模型的数据的数量和质量是模型学习效果好坏的重要因素。由于机器学习模型可以从数据中学习,它们也可用于发现数据中隐藏的模式和趋势。这些趋势和模式能带来对数据的宝贵见解。因此,使用机器学习可以为特定问题做出数据驱动的决策。
总结:机器学习领域专注于研究和构建能够从数据中学习、而无需明确编程的计算机系统。机器学习算法和技术用于构建模型,以发现数据中隐藏的模式和趋势,从而做出数据驱动的决策。
机器学习已被用于许多不同的应用,其中许多你可能在日常生活中遇到过,甚至可能没有意识到。
以下是几个常见的应用示例:
- 信用卡欺诈检测:每次使用信用卡时,当前的购买行为都会根据你的信用卡交易历史进行分析,以确定当前交易是合法的还是潜在的欺诈行为。如果购买行为与你过去的购买行为非常不同,例如购买你从未表现出兴趣的类别中的高价商品,或者销售点位置来自另一个国家,则该交易将被标记为可疑活动。
- 手写数字识别:当你将手写支票存入ATM时,会使用机器学习过程来读取支票上写的数字以确定存款金额。由于人们笔迹的多样性,手写数字比印刷体数字更难识别。机器学习系统可以筛选不同的变体,以找到相似的模式来区分例如“1”和“9”。
- 网站推荐:在网站上购买商品后,你经常会得到一个相关商品的列表。这通常显示为“购买此商品的顾客也购买了这些商品”或“你可能也喜欢”。这些相关商品已通过机器学习模型与你购买的商品关联起来,并展示给你,因为你可能也对它们感兴趣。这是销售和营销中常用的机器学习应用。
这些只是众多例子中的几个。正如我们所讨论的,数据科学植根于统计学、机器学习、人工智能和计算机科学等领域。因此,机器学习是这一领域的一部分,包含了用于从数据中学习的算法和技术。
机器学习技术有不同的类别,适用于不同类型的问题。我们将讨论的主要类别是分类、回归、聚类分析和关联分析。
在分类中,目标是预测输入数据的类别。例如,预测天气是晴天、雨天、有风还是多云。输入数据是指定温度、相对湿度、大气压力、风速、风向等的传感器数据。要预测的目标或类别(如晴天、有风、雨天和阴天)被称为分类。
另一个例子是将肿瘤分类为良性或恶性。在这种情况下,分类被称为二分类,因为只有两个类别。你也可以有很多类别,如天气预测问题所示。分类的另一个例子是识别手写数字,将其归类为0到9这10个类别之一。
当你的模型需要预测一个数值而不是一个类别时,任务就变成了回归问题。回归的一个例子是预测股票价格。股票价格是一个数值,不是一个类别,所以这是一个回归任务。如果你要预测股票价格是上涨还是下跌,那将是一个分类问题。但如果你预测的是股票的实际价格,那就是一个回归问题。这是分类和回归之间的主要区别。
总结:在分类中,你预测的是一个类别;在回归中,你预测的是一个数值。
在聚类分析中,目标是将数据集中相似的项目组织成组。聚类分析的一个非常常见的应用被称为客户细分。这意味着根据客户类型将你的客户群划分为不同的组或细分市场。例如,将客户细分为老年人、成年人和青少年是有益的。这些群体可能有不同的好恶和购买行为。当公司这样将客户细分为不同的群体时,他们可能能够为每个群体的特定兴趣提供有针对性的营销广告。
关联分析的目标是提出一组规则,以捕捉项目或事件之间的关联。这些规则用于确定项目或事件何时一起发生。关联分析的一个常见应用被称为市场篮子分析,用于理解客户的购买行为。例如,关联分析可以揭示,拥有支票或存款账户的银行客户也往往对其他投资工具(如货币市场账户)感兴趣。这些信息可用于交叉销售。
对于我们所讨论的技术,有两种进行学习本身的方式。这些类别被称为监督学习与无监督学习。
- 监督学习:在监督方法中,提供了目标(即模型要预测的内容)。这被称为拥有标记数据,因为数据中每个样本的目标都被标记了。例如,在预测天气类别(晴天、有风、雨天、阴天)时,数据集中的每个样本都被标记为这四个类别之一。因此,数据是标记的。预测天气类别是一项监督任务。一般来说,分类和回归是监督方法。
- 无监督学习:在无监督方法中,模型要预测的目标是未知或不可用的。这意味着你拥有未标记数据。因此,你无法使用这些标签进行训练。回到将客户细分为不同群体的聚类分析例子,你的数据中的样本并没有被标记为正确的组。相反,分割是使用聚类技术根据项目共有的特征对项目进行分组来执行的。因此,数据是未标记的,将客户分组到不同细分市场的任务是无监督的。一般来说,聚类分析和关联分析是无监督方法。
总结:在本节中,我们探讨了机器学习技术的不同类别,并讨论了分类、回归、聚类和关联分析作为其中一些技术。我们还定义了机器学习中无监督和监督方法是什么,以及我们之前讨论的哪些类别属于这两类。
在我们深入研究处理和分析数据的方法之前,让我们首先定义一些用于描述数据的术语,从样本和变量开始。
- 样本:样本是数据中实体的一个实例或示例。这通常是数据中的一行。下图显示了与天气相关的数据集的一部分。每一行都是一个样本,代表特定日期的天气数据。在该表中,每个样本有五个与之相关的值。这些值是关于该样本的不同信息片段,如样本日期、最低温度、最高温度和该日期的降雨量。
- 变量/特征:我们称这些不同的值为样本的变量,有时也称为样本的特征。事实上,样本和变量有很多名称,其中一些我们已经在前几周使用过。
以下是样本和变量的一些其他术语:
- 样本的其他术语:记录、示例、行、实例、观察值等。所有这些术语在机器学习上下文中都指代数据中实体的特定示例。
- 变量的其他术语:特征、列、维度、属性、字段。所有这些术语都指代数据集中每个样本的特定特征。
关于变量的一个重要点是它们是具有类型的数字值。每个变量都有一个与之关联的数据类型。最常见的数据类型是数值型和分类型。还有其他数据类型,如字符串和日期,但在数据挖掘的背景下,我们将重点关注更常见的数值型和分类型数据类型。
- 数值型变量:顾名思义,数值型变量是取数字的变量。它们可以被测量,并且它们的值可以按某种方式排序。数值型变量可以只取整数值,也可以是连续值。它也可以只有正数、负数或两者兼有。
- 分类型变量:具有标签、名称或类别作为值(而不是数字)的变量称为分类型变量。例如,描述物品颜色(如汽车颜色)的变量可以具有诸如红色、银色、蓝色和黑色等值。这些是非数值,用于描述实体的某些质量或特征。这些值可以被视为可以分类的名称或标签。因此,分类型变量也被称为定性变量或名义变量。
总结:样本是数据中实体的实例或示例。变量捕获每个实体的特定特征。因此,一个样本有许多变量来描述它。来自实际应用的数据通常是多维的,这意味着有许多维度或变量描述每个样本。每个变量都有一个与之关联的数据类型,最常见的数据类型是数值型和分类型。
既然我们已经讨论了机器学习的基础知识,让我们概览一个名为 Scikit-learn 的 Python 机器学习库。
Scikit-learn 是一个用于端到端机器学习的开源 Python 库。它建立在 NumPy、SciPy 和 Matplotlib 的优势之上,就像许多其他 Python 库一样。它由一个活跃的开发者社区快速开发和改进。
当我说端到端机器学习时,我指的是整个数据科学过程,包括机器学习、数据清洗和数据转换。Scikit-learn 通过提供数据转换的实用函数以及一系列数据清洗和准备函数来支持整个过程,这些函数有助于许多任务,包括缩放、归一化、特征工程和缺失值处理。
Scikit-learn 还为许多机器学习算法提供了内置函数,可直接用于建模和分析。虽然需要一些专业知识才能为正确的任务适当地使用这些算法,但网上有许多资源使学习曲线更容易。此外,Scikit-learn 网站上的文档包括入门教程。我们发现这个文档非常易于遵循。例如,文档的聚类部分很好地概述了可用的算法、它们的度量标准、可扩展性、参数甚至潜在的用例。
Scikit-learn 还包括降维算法的专门实现。虽然我们不会在本课程中详细介绍这些算法,但它将在你即将开始的微硕士课程中的机器学习课程中派上用场。在机器学习中,我们使用许多技术来评估和选择正确的模型。你也会发现许多有助于此的方法。
总结:Scikit-learn 为完整的机器学习过程提供了广泛的工具集。得益于 Python,我们可以将这些工具与我们迄今为止学到的 Python 中其他数据科学工具结合起来。这个开放且可扩展的机器学习库由一个非常活跃的社区充满活力地开发和记录。

在本节课中,我们一起学习了机器学习的基本定义,即构建能够从数据中学习、无需明确编程的系统。我们探讨了机器学习的几个日常应用,如欺诈检测和推荐系统。我们详细介绍了机器学习的主要技术类别:用于预测类别的分类、用于预测数值的回归、用于分组的聚类分析以及用于发现关联规则的关联分析。我们还区分了监督学习(使用标记数据)和无监督学习(使用未标记数据)。最后,我们介绍了机器学习中的关键术语(如样本、特征)并概览了强大的 Python 库 Scikit-learn,它为整个机器学习流程提供了全面的工具集。


在本节课中,我们将重点介绍一类称为分类的机器学习问题。
通过本视频的学习,你将能够定义什么是分类,讨论分类属于监督学习还是无监督学习,并描述二项分类与多项分类的区别。
正如我们之前讨论的,分类是机器学习问题的一种类型。
在分类问题中,输入数据被呈现给机器学习模型,任务是预测与输入数据对应的目标。
目标是一个分类变量,因此分类任务是根据给定的输入数据预测目标的类别或标签。
例如,我们之前讨论并在本图中说明的分类问题是预测天气类型。
模型必须预测的目标是天气,在这种情况下天气的可能值是晴天、有风、雨天或多云。
输入数据可以包含温度、相对湿度、大气压力、风速、风向等测量值。
因此,给定温度、相对湿度及其他所有测量的具体数值,模型的任务是预测当天的天气是晴天、有风、雨天还是多云。
这是天气分类问题数据集可能的样子。
每一行是一个样本,包含输入变量(温度、湿度、压力)和目标变量(天气)。
每一行都有输入变量的具体值以及目标变量的对应值。
分类任务是根据输入变量的值预测目标变量的值。
由于提供了目标,我们拥有带标签的数据,因此分类是一项监督任务。回想一下,在监督任务中,每个样本的目标或期望输出是给定的。
请注意,目标变量有许多名称,例如目标、标签、输出、类别变量、分类和类。
分类问题可以是二元的,也可以是多元的。
在二元分类中,目标变量有两个可能的值,例如“是”和“否”。
在多元分类中,目标变量有两个以上的可能值。例如,目标可以是“矮”、“中”和“高”。
多元分类也被称为多项分类或多标签分类。
但请记住,在分类中,目标始终是一个分类变量。
二元分类的一些例子包括:预测明天是否会下雨(这里只有两种可能的结果:是,明天下雨;或否,明天不下雨)。
另一个例子是识别信用卡交易是合法的还是欺诈性的(同样,目标只有两个可能的值:合法和欺诈)。
多元分类的一些例子包括:预测客户将购买的产品类型。
在这种情况下,目标变量的可能值将是产品类别,例如厨房用品、电子产品、服装等。由于产品类别不止一个,因此这是一个多元分类问题。
另一个例子是将一条推文的情感分类为积极、消极或中性。同样,这里目标可能值的数量超过两个(尽管是三个),因此这也是一个多元分类任务。
总而言之,在分类中,模型必须预测与输入数据对应的类别。由于为每个样本提供了目标,因此分类是一项监督任务。在分类中,目标变量始终是分类变量。
既然我们了解了分类的含义,接下来让我们谈谈构建分类模型意味着什么,以及构建模型与应用模型有何不同。
通过本视频的学习,你将能够讨论构建分类模型意味着什么,解释构建模型和应用模型之间的区别,总结为什么需要调整模型参数,描述分类算法的目标,并列举一些常见的分类算法。
机器学习模型在广义上是一个数学模型或关于输入的参数化函数。
这意味着模型具有参数,并使用方程来确定其输入和输出之间的关系。
模型使用其参数来修改输入以生成输出。
模型调整其参数以纠正或完善这种输入输出关系。
这是一个简单模型的例子。这个数学模型代表一条直线。Y 是输出,X 是输入。M 决定直线的斜率,B 决定 y 轴截距或直线与 y 轴的交点。M 和 B 是模型的参数。
给定 X 的具体值,模型使用其参数和 X 来确定 Y。
通过调整参数 M 和 B 的值,模型可以调整输入 X 如何映射到输出 Y。
在这里,我们可以看到当参数 B 改变时,对于相同的输入 X 值,输出 Y 如何变化。
回想一下,B 是 y 轴截距或直线与 y 轴的交点。对于红线,B 的值为正1;对于蓝线,B 的值为负1。
对于输入 x = 1,红线的 y 值为3(如红色箭头所示)。对于蓝线,参数 B 从正1变为负1。对于 x = 1,y 值为1(如蓝色箭头所示)。
因此我们看到,仅通过一个模型参数的简单改变,输入到输出的映射就发生了变化。
机器学习模型的工作方式非常相似。
它将输入值映射到输出值,并调整其参数以纠正或完善这种输入输出映射。
机器学习模型的参数是使用学习算法从数据中调整或估计出来的。
本质上,这就是构建模型所涉及的内容。
这个过程有许多术语,如模型构建、模型创建、模型训练和模型拟合。
在构建模型时,我们希望调整参数以减少模型的误差。
对于监督任务(如分类),这意味着使模型的输出尽可能匹配目标或期望输出。
由于分类任务是根据输入变量预测类别,你可以将分类问题在视觉上理解为将输入空间划分为对应于不同类别标签的区域。
例如,在此图中,分类模型需要形成边界来定义分隔红色三角形、蓝色菱形、绿色圆形和黄色正方形的区域。
在此示例中,如果一个样本落在右上角的区域内,它将被分类为蓝色菱形。
分类决策基于这些区域,而区域由边界定义,如本图中的虚线所示。因此,这些边界被称为决策边界。
那么,构建分类模型意味着使用数据调整模型参数,以形成决策边界来分隔目标类别。
请注意,术语“分类器”通常用来表示分类模型。
一般来说,构建分类模型以及其他机器学习模型涉及两个阶段。
第一阶段是训练阶段,在此阶段使用所谓的训练数据构建模型并调整其参数。训练数据是用于训练或创建模型的数据集。
第二阶段是测试阶段。在此阶段,将学习到的模型应用于新数据,即未在训练模型中使用过的数据。
这是看待这两个阶段的另一种方式:在训练阶段,学习算法使用训练数据调整模型参数以最小化误差。在训练阶段结束时,你得到训练好的模型。
在测试阶段,将训练好的模型应用于测试数据。测试数据与训练数据是分开的。请记住,训练数据是模型先前见过的,而测试数据是模型先前未见过的。
然后根据模型在测试数据上的表现对其进行评估。
构建分类器模型的目标是让模型在训练数据和测试数据上都表现良好。
我们将在讨论模型评估时更详细地讨论训练和测试的使用。
总结我们到目前为止所学的内容:为了调整模型的参数,我们需要应用学习算法。
现在让我们概述一些用于构建分类模型的常用算法。
回想一下,分类任务是根据输入变量预测类别。分类模型处理接收到的输入数据并提供输出。
由于分类是监督任务,因此为每个样本提供了目标或期望输出。
目标是使模型输出尽可能匹配目标。
分类模型调整参数以使其输出匹配目标。为了调整模型参数,需要应用学习算法。
这发生在构建模型的训练阶段。
有许多算法可用于构建分类模型,包括 KNN(K 近邻)、决策树和朴素贝叶斯。
KNN 代表 K 近邻,该技术依赖于这样的概念:具有相似特征(即输入值相似)的样本很可能属于同一类别。因此,样本的分类取决于邻近点的目标值。
另一种非常流行的分类技术被称为决策树。决策树是一种分类模型,它使用树状结构来表示多个决策路径。我们看到每条路径都通向一种不同的方式来分类输入样本。
朴素贝叶斯模型使用概率方法进行分类。贝叶斯定理用于捕捉输入数据和输出类别之间的关系。简单来说,贝叶斯定理比较在另一个事件存在的情况下某个事件的概率。例如,天气炎热时发生火灾的概率。你可以想象事件 B 依赖于多个变量,例如天气炎热且有风。
还有许多其他分类技术,但现在我们将继续讨论决策树作为一种分类算法,以便在 Python 中使用 Scikit-learn 进行更好的分类。
在继续我们的笔记本之前,让我们先回顾一下决策树。
现在让我们看看决策树模型,这是一种用于分类的流行方法。
通过本视频的学习,你将能够解释如何使用决策树进行分类,描述为分类构建决策树的过程,并解释决策树如何得出分类决策。
决策树分类背后的思想是将数据分割成子集,其中每个子集仅属于一个类别。
这是通过将输入空间划分为纯区域(即仅包含一个类别样本的区域)来实现的。
对于真实数据,完全纯的子集可能无法实现,因此目标是将数据分割成尽可能纯的子集。也就是说,每个子集尽可能多地包含单个类别的样本。
在图形上,这相当于将输入空间划分为尽可能纯的区域。
分隔这些区域的边界称为决策边界,决策树模型基于这些决策边界做出分类决策。
决策树是一种具有节点和有向边的层次结构。
顶部的节点称为根节点。底部的节点称为叶节点。既不是根节点也不是叶节点的节点称为内部节点。
根节点和内部节点具有测试条件。每个叶节点都有一个与之关联的类别标签。
分类决策是通过遍历决策树做出的,从根节点开始。在每个节点,测试条件的答案决定了遍历哪个分支。当到达叶节点时,叶节点处的类别决定了分类决策。
节点的深度是从根节点到该节点的边数。根节点的深度为零。决策树的深度是从根节点到叶节点的最长路径中的边数。决策树的大小是树中的节点数。
这是一个决策树的示例,可用于将动物分类为哺乳动物或非哺乳动物。
根据这个决策树,如果一个动物是温血动物、胎生且有脊椎,那么它就是哺乳动物。如果一个动物不具备这三个特征中的任何一个,那么它就不是哺乳动物。
决策树的构建始于将所有样本放在一个节点(根节点),并在数据分割成子集时添加额外的节点。
从高层次来看,构建决策树包括以下步骤:从一个节点开始,将所有样本放在该节点。根据输入变量将样本分割成子集。这里的目标是创建尽可能纯的记录子集,即每个子集尽可能多地包含仅属于一个类别的样本。另一种说法是,子集应该是同质的或尽可能纯的。重复将数据分割成越来越纯的子集,直到满足停止标准。
构建决策树模型的算法被称为归纳算法。因此,你可能会听到术语“树归纳”用来描述构建决策树的过程。
请注意,在每次分割时,归纳算法只考虑分割数据特定部分的**方式。这被称为贪心方法。贪心算法一次解决一个问题子集,当解决整个问题不可行时,这是一种必要的方法。这里的“可行”指的是在合理的时间或空间内无法计算。
使用贪心算法对于决策树是必要的,因为确定给定数据集的**树是不可行的。因此,树必须通过在每个步骤确定分割当前节点的**方式,并将这些决策组合在一起形成最终的决策树来逐步构建。
在构建决策树时,数据是如何分割的?决策树如何确定在节点处分割样本集的**方式?同样,目标是将节点处的数据分割成尽可能纯的子集。在此示例中,右侧显示的分割产生了更同质的子集,因为这些子集包含更多属于单个类别的样本,优于左侧显示的结果子集。因此,右侧的分割产生了更纯的子集,是首选的分割方式。
因此,我们需要一种方法来衡量分割的纯度,以便比较分割数据集的不同方式。事实证明,从数学上讲,衡量分割的不纯度比衡量纯度效果更好。因此,节点的不纯度度量指定了结果子集的混合程度。由于我们希望结果子集具有同质的类别标签,而不是混合的类别标签,因此我们希望最小化不纯度度量的分割。
用于确定**分割的常用不纯度度量称为基尼指数。基尼指数越低,分割的纯度越高。因此,决策树将选择最小化基尼指数的分割。除了基尼指数,其他不纯度度量还包括熵(或信息增益)和误分类率。
确定分割节点**方式的另一个因素是选择基于哪个变量进行分割。决策树将测试所有变量,使用纯度度量(如基尼指数)来比较各种可能性,以确定分割节点的**方式。
回想一下,树归纳算法重复分割节点以获得越来越同质的数据集。那么,这个过程何时停止构建子集?算法何时停止生长树?
有几种标准可用于确定何时不应再将节点分割成子集。
归纳算法可以在节点中所有样本具有相同类别标签时停止扩展该节点。这意味着这组数据尽可能纯,进一步分割不会导致数据的任何更好划分。由于在真实数据中实现完全纯的子集很困难,此停止标准可以修改为:当节点中一定比例的样本(例如90%)具有相同的类别标签时。
当节点中的样本数量低于某个最小值时,算法可以停止扩展节点。此时,样本数量太少,对分类结果影响不大,因此无需进一步分割。
当不纯度度量的改进太小,对分类结果影响不大时,归纳算法也可以停止扩展节点。
此外,当达到最大树深度时,树或算法可以停止扩展节点。这是为了控制结果树的复杂度。
可能还有其他标准可用于确定树归纳何时应停止,但我们就此打住。
让我们看一个例子来说明归纳过程。
假设我们想根据贷款申请人的收入和债务金额,将其分类为可能偿还贷款或不可能偿还贷款。
为此分类问题构建决策树的过程可能如下进行。
考虑此问题的输入空间,如左图所示。
将此数据分割成更同质子集的一种方法是考虑决策边界,其中收入等于 T1。在此决策边界的右侧,主要是红色样本;左侧主要是蓝色样本。子集并不完全同质,但这是基于变量“收入”分割原始数据的**方式。
决策边界在决策树中由条件“收入大于 T1”表示,位于根节点。这是用于分割原始数据集的条件。收入大于阈值 T1 的样本被放置在右侧子集,收入小于或等于 T1 的样本被放置在左侧子集,如左图所示。
由于右侧子集几乎完美地预测贷款人会正确还款,因此右侧子集现在被标记为红色,意味着贷款申请人可能偿还贷款。
第二步是确定如何分割左图中红色边框勾勒的区域。分割此数据的**方式由第二个决策边界指定,其中债务等于 T2。这在右侧的决策树中通过添加条件为“债务大于 T2”的节点来表示。债务值大于 T2 的样本显示在决策边界周围的区域。该区域包含所有蓝色样本,因此相应的节点被标记为蓝色,意味着贷款申请人不可能偿还贷款。
第三次也是最后一次分割着眼于如何分割左图中红色边框勾勒的区域。**分割由边界“收入等于 T3”指定。这将红**域分割成两个纯子集。该分割在决策树中通过添加条件为“收入大于 T3”的节点来表示,左侧的结果节点标记为蓝色,右侧的结果节点标记为红色,对应于左图中带有红色边框的结果子集。
我们最终得到右侧的最终决策树,它实现了左图中虚线所示的决策边界。这些决策边界按所示方式分割数据集。每个区域的标签由该区域内大多数样本的标签决定。这些标签反映在右侧决策树的叶节点中。
你可能已经注意到,决策树的决策边界平行于变量形成的轴。这被称为是直线性的。边界是直线性的,因为每次分割只考虑单个变量。然而,存在考虑多个属性进行分割的树归纳算法变体。但是,每次分割必须考虑组合变量的所有组合,因此这种归纳算法计算量更大,或者说计算成本更高。
关于这个决策树分类器,有几点需要注意:结果树通常易于理解和解释。这是决策树分类的最大优势之一。通常可以查看结果树,了解哪些变量对分类问题很重要,并理解分类是如何执行的。因此,许多人会从决策树分类器开始,以感受分类问题,即使他们最终可能会使用更复杂的模型。
本课描述的树归纳算法计算量相对较小,因此训练决策树进行分类可以相对较快。
树归纳算法使用的贪心方法确定了在节点处分割数据部分的**方式,但不能保证整个数据集的**整体解决方案。
决策边界是直线性的,这可能会限制结果模型的表达能力,意味着它可能无法解决需要更复杂决策的复杂分类问题。
总而言之,决策树分类器使用树状结构来指定一系列测试条件,以确定类别标签。决策树是通过重复分割数据并将数据分割成越来越同质的子集来构建的。结果树通常易于解释。
现在让我们看看我们的笔记本,在 Python 中对我们的天气数据进行决策树分类。
在这个笔记本中,我们将使用 Scikit-learn 对天气数据执行基于决策树的分类。
这些天气数据是从位于加利福尼亚州圣地亚哥的气象站的传感器数据中整理出来的。气象站有传感器,可以捕获与天气相关的测量值,如气温、气压和相对湿度。数据收集自三年期间(从2011年9月到2014年9月),这确保了捕获了不同季节和天气条件的足够数据。
现在让我们导入必要的库。我们进入笔记本,希望你在提供的本周 zip 文件中找到了名为“Better Data Classification using Decision Tree”的笔记本。
我们在第七周,使用决策树进行字母分类。所以我要进入我的第一个单元格,开始导入必要的库以开始分析。
正如你刚刚学到的,分类任务是一项监督学习任务,我们通过训练来监督算法学习如何标记给定样本。在这里,我们将利用 Scikit-learn 库中的现有方法。从我们的导入中可以看到,我们将在这个例子中使用决策树分类器。
我们导入 pandas 作为 pd,sklearn.metrics 的 accuracy_score,sklearn.model_selection 的 train_test_split,以及 sklearn.tree 的 DecisionTreeClassifier。
这些是我们这个笔记本的所有导入。作为旁注,你可以在笔记本的任何时候导入这些库,例如当你使用它们时。但在实践中,将它们全部列在笔记本顶部也是一个好习惯,这样确保你的笔记本用户在打开笔记本时了解你将使用什么。但在实践中这并不重要。
让我们导入 CSV 文件。同样,CSV 文件也在本周的文件夹中提供给你,文件夹名为“Weather”,文件名是“daily_weather.csv”。你已经学过这个,我们将使用 read_csv 函数将其导入到 pandas DataFrame 中,我们称之为 data。
让我们导入数据。
我们将快速查看 pandas DataFrame 及其中的空值,以了解数据。但在此之前,请记住,这些数据是从气象站收集的。让我们看看其中的列。我们会看到气压、上午9点的气温等都在数据集中,所有的列。
如果你查看下面的 Markdown 单元格,我们描述了每一列是什么,为什么这样命名,例如,上午9点的气压是从8:55到9:04期间的平均气压,单位是华氏度。你可以通过阅读这个 Markdown 来了解更多关于数据集的信息,当然你也可以在继续之前查看这个 DataFrame。
现在我们可以查看其中的空值。如果存在任何空值,我们会看到数据集中有一些 NaN。所以数据集中有一些包含空值的行。
让我们稍微清理一下 DataFrame,为分析做好准备。这些数据已经过一些整理,所以实际上需要清理的地方不多,但例如“number”列只是每个条目的唯一数字 ID,我们不需要它,所以我会继续删除该列。
我们已经看到有一些空值,我们将删除这些空值。请记住,pandas 中的 dropna 函数将为你提供删除所有缺失值的机会。
我将删除空值之前的行数存储到一个名为 drop_before_rows 的变量中。它是1095行。然后我们使用 data.dropna() 删除空值,并将该 DataFrame 切片存储回 data 本身作为切片。之后,我们获取其形状。清理空值后,我们有1064行。让我们看看实际删除了多少行,我们会发现总共删除了31行。
我们称之为清理了数据集,现在可以开始我们的分类任务了,这是从这个笔记本中获得的学习目标。
我们将使用分类器通过观察早晨的天气来预测给定下午的湿度。这使我们能够在某些情况下仅通过观察早晨的天气来预测下午的天气。
请记住,我们使用 .copy() 函数生成数据的切片并将其分配给 DataFrame,而不是复制切片。现在,我们将切片复制到一个新的 DataFrame 中,该 DataFrame 不是切片,而是一个真正的 DataFrame,或者如果我们分配的数据是原始数据的视图,我们会称之为视图。我们将称这个清理后的数据将再次成为一个真正的 DataFrame。
在此步骤之后,我们将向 clean_data 添加一个新列,名为 high_humidity_label。这个新列将存储湿度值高于24.99时的值1。
如果我们查看下午3点的相对湿度,如果它高于24.99,它将给我们一个真/假值。我们将这个由比较运算符“大于”生成的真/假值乘以1,将该列转换为整数值0或1。
当我们实际打印该列时,我们看到有1和0。
总结一下我们在这里所做的:我们创建了一个新变量,是0或1,这是我们的目标变量。我们称这个目标变量为 high_humidity_label,因此我们标记我们的目标变量为高湿度,所以如果它是1,我们知道该值是高湿度或高于24.99;如果它是0,我们知道它低于该值。
按照参数化函数类比 y = f(x),我们现在将创建一个名为 y 的新 DataFrame,用于存储我们试图预测的内容。因此,通过查看 x,我们将尝试预测 y,这就是类比。
我们将通过将 clean_data 中的 high_humidity_label 列存储到 y 中来实现。
现在我们将 clean_data 中的高湿度标签存储到 y 中。让我们在这里查看 y,看看其中存储了什么。应该不奇怪,它存储了来自 clean_data 的高湿度标签。
现在我们有了存储的标签。为了整洁起见,我将注释掉打印 y 的代码并继续。
我们还可以打印 y 和 clean_data 的前五行,以比较湿度值与高湿度标签并进行验证。
我们看到这些五行中的湿度值范围从12到76,然后查看 y.head(),当我们查看这些值时,记住高于24.99的值(36和76)被转换为标签1(高湿度标签),低于该值的值被转换为0。
现在我们将选择早晨数据的特征。请记住,我们使用早晨数据来创建下午的预测器。我们将所有这些上午9点的变量或特征放入一个名为 morning_features 的列表中。
运行这个后,morning_features 列表包含了上午9点的气压、气温等。之后,我们将创建一个矩阵或 DataFrame(在本例中),仅包含这些早晨特征作为列。我们将字面上将这些早晨相关列中的值复制到一个名为 X 的 DataFrame 中。再次记住,我们试图做 y = f(x),这就是为什么我们称标签为 y,而用于训练的输入为 X。因此,给定 X,我们将尝试得出 y 值,这就是我们要做的。
这里 X 中 clean_data 的值是相当标准的数据类型。然而,如果它们是列表或其他更复杂的数据类型,我们可能不得不使用深拷贝。在这种情况下,我们会在 copy() 函数括号内说 deep=True,以确保如果你的输入矩阵中有任何复杂的数据类型(如数组或列表),它们会被复制到 X 中。在这种情况下,我们不需要。
现在让我们再次检查 x 和 y 的列。我们可以说 x.columns,我们看到我们在 morning_features 中选择的所有内容都是 x 的列。而 y 将有一个标签 high_humidity_label 作为列索引。很好,到目前为止一切顺利。
现在我们将开始构建我们的第一个模型,一个使用决策树的分类模型。
为此,我们需要将数据集分割为测试样本和训练样本。提醒一下,我们需要使用部分数据进行训练(训练数据),其余部分用于测试。为了做到这一点,我们使用 train_test_split 函数来生成这两个数据集。现在这两个数据集将是我们存储在 X 中的原始数据和存储在 y 中的标签的切片。
让我们稍微讨论一下这里的参数。我们有 train_test_split,我们给它 X(我们的输入)和 y(训练标签)。除了这两个,我们还提供了一个值,表示我们想要用多少数据进行测试,称为 test_size。因此,我们告诉这个函数保留33%的数据,不要显示给训练器,稍后用于测试。我们还将 random_state 种子在此笔记本中设置为324。尽管这个种子是可更改的,但它将导致原始数据集的不同划分,进而导致不同的预测准确性。因此,我强烈建议你保持原样,如果你想跟着做,稍后你可以用不同的种子进行测试。
好的,让我们看看这个函数的摘要。这里的 train_test_split 接受两个 DataFrame 并返回四个 DataFrame:X_train、X_test、y_train 和 y_test。现在请随时暂停视频,我实际上建议你这样做,并在下一个代码单元格中添加或取消注释行,以便在我们继续之前探索这些 DataFrame。
希望你能运行取消注释并运行代码块中的那些行。
现在让我们使用决策树分类器在训练数据集上进行训练,我将命名这个训练数据。我们的决策树本质上是一个湿度分类器。在这里,我们试图在训练数据上进行拟合。我们有了湿度分类器。
对于决策树分类器,我们将设置 max_leaf_nodes 为10。这是我们树归纳的停止标准。如果保留默认值,它将是无限的,可能导致对训练数据的过拟合,我们不希望那样。但一般来说,你需要稍微实验一下。
此算法中的 random_state 参数用于分割节点。这里的0是用于构建决策树的内部状态之一。尽管就像之前的随机种子一样,这个也是可更改的。如果你想在本地复制本视频的结果,请保持为0。
好的,我们使用 DecisionTreeClassifier,给它 max_leaf_nodes 和 random_state,并将其分配给 humidity_classifier。接下来,我们创建了一个名为 humidity_classifier 的决策树分类器对象。我们将使用该对象的 fit 方法使分类器根据样本调整自身以进行学习。
因此,我们有 humidity_classifier.fit,并给它我们在之前代码单元格中分割的训练集 X 和 y。在此函数结束时,我们将拥有一个基于决策树的分类器模型。
如果我们运行这个,我们看到 humidity_classifier 对象的类型确实是决策树分类器。
现在我们将使用测试数据进行预测,以测试我们刚刚构建的模型。我们将使用决策树分类器对象的 predict 函数,也就是我们的 humidity_classifier。这个函数将接受我们的测试数据。因此,我们执行 humidity_classifier.predict,它将接受 X_test 数据。这意味着我们要求分类器在测试集上进行预测,该测试集在训练阶段完全没见过。
让我们运行这个,并显示这些预测中的前十个值。同时,让我们看看 y_test 是什么,因为那是我们从原始数据中保留的标签。因此,我们有我们的预测,这是基于模型学习到的预测湿度标签。当我们给它测试数据 X_test 时,它得出了这些预测。
现在,在我们继续查看错误之前,让我们总结一下我们所做的。我们有 X(输入数据)和 y(标签)。我们为输入和标签创建了训练集。我们还为输入和标签创建了测试集。我们所做的是使用训练 X 和训练 y 创建了一个模型。然后我们使用测试 X 来创建预测的 y。刚刚显示的预测就是预测的 y。但请记住,在我们的 y_test 中,我们有测试数据的实际标签。因此,现在我们可以将预测与 y_test 进行比较。当我们查看时,前两个值是0,然后预测中有四个1,实际数据中有五个1,所以有一个错误,对吧?其中一个没有被正确预测。然后我们有0、1、1,这里有0、0、1,所以又是另一个错误值。
因此,这里大约有十个值,其中两个是错误的。所以总的来说,这被称为分类任务的错误。当模型的类别标签预测与真实类别标签不同时,就会发生错误。
我们可以通过计算正确标签的数量来衡量这些错误,就像我刚才做的那样。我们有两个错误,所以如果我们有10行,错误数就是那么多,我们可以将其转化为错误度量。例如,就像我们做的那样,如果我们只看生成标签的前10行,我们可以看到预测中的值与 y_test 中的值非常接近,但你知道,当行数很大时,我们无法总是计算有多接近。我们如何通过计算准确率分数来测试分类器的准确性?我们实际上有一个函数。它被称为 accuracy_score,如果你看那个,我们算法的准确率大约是81%。
我们还可以根据预测标签和真实标签定义分类中的不同类型错误。但在这里我们将停止,并称这81%为我们的准确率分数。

最后,考虑到它只查看上午9点的测量值来预测下午3点的天气,这是一个足够好的分类器吗?我会说是的,特别是对于圣地亚哥来说,天气总是变化无常。


在本节课中,我们将学习机器学习中的聚类分析。我们将探讨其目标、核心概念、常用算法(K均值聚类)以及如何在实际数据集中应用它。
聚类分析的目标是将数据集中的相似项目组织成组或“簇”。通过将数据分割成簇,我们可以更仔细地分析每个组。这是一种无监督学习任务,意味着数据没有预先定义的标签。
聚类分析,通常简称为聚类,其目标是根据某种相似性度量,将数据集中的所有样本划分到不同的组中。
在聚类中,我们希望实现两个目标:
- 最小化同一簇内样本之间的差异。
- 最大化不同簇之间样本的差异。
视觉上,这可以理解为让每个簇内的样本尽可能靠近,而不同簇的样本尽可能远离。
聚类分析需要某种度量标准来衡量两个样本之间的相似性。以下是一些常见的相似性度量:
- 欧几里得距离:两点之间的直线距离。公式为:
distance = sqrt((x2-x1)^2 + (y2-y1)^2) - 曼哈顿距离:在严格水平和垂直路径上计算的距离。公式为:
distance = |x2-x1| + |y2-y1| - 余弦相似度:测量两个点之间夹角的余弦值。
由于欧几里得距离等距离度量常被用于衡量样本间的相似性,因此可能需要对输入变量进行归一化,以防止某个值在相似性计算中占据主导地位。归一化可以将所有变量置于同一尺度上,确保它们在确定样本相似性时具有相等的权重。
关于聚类分析,有几点需要注意:
- 无监督任务:与分类和回归不同,聚类通常是无监督任务。数据集中没有任何样本带有目标标签。
- 无绝对正确结果:不存在绝对“正确”的聚类结果。**的簇集合高度依赖于具体应用以及结果将如何被使用。
- 簇没有标签:聚类过程结束时,你可能会得到五个不同的簇,但你不知道每个簇代表什么。只有通过进一步分析每个簇中的样本,才能为簇提出合理的标签。
因此,解释和分析簇对于理解和利用聚类分析的结果至关重要。
聚类分析的结果有几种应用方式:
- 数据分割与洞察:最明显的应用是数据分割及其带来的好处。例如,将客户群细分为不同类型的读者,单独分析每个细分市场可以深入了解每个群体的喜好、厌恶和购买行为。这些洞察可用于根据客户偏好进行更有效的营销。
- 新样本分类:当接收到一个新样本时,计算它与所有簇中心的相似性度量,并将其分配到最接近的簇。然后,通过手动分析确定的簇标签可用于对新样本进行分类。
- 为分类任务提供标签:一旦确定了簇标签,每个簇中的样本就可以用作另一个分类任务的标记数据。样本将是分类模型的输入,而簇标签将是每个样本的目标类别。这个过程可以为分类提供急需的标签数据。
- 异常检测的基础:如果一个样本距离任何簇中心都非常远或非常不同,那么该样本就是一个簇的异常值,可以被标记为异常。然而,这些异常需要进一步分析。在某些应用中,这些异常可能被视为噪声并从数据集中移除;在其他情况下(如信用卡欺诈检测),这些异常是需要仔细研究的案例。
K均值是一种用于聚类分析的经典算法。该算法非常简单直接。
K均值算法的步骤如下:
- 选择K个初始质心。质心即簇的中心点。
- 将数据集中的每个样本分配到最近的质心。这意味着你需要计算样本与每个簇中心的距离,并将样本分配给距离最近的质心所在的簇。
- 计算每个簇中所有样本的均值,以确定新的质心。
- 重复步骤2和3,直到达到某个停止标准。
最终聚类结果对初始质心的选择很敏感。这意味着使用一组初始质心得到的结果可能与使用另一组初始质心得到的结果不同。
选择初始质心最简单且最广泛使用的方法是:使用随机选择的不同初始质心多次运行K均值算法来聚类数据集,然后选择能给出**聚类结果的质心。
可以使用一种称为簇内平方和误差的度量来评估聚类结果。
一个样本在簇内的误差是该样本与簇质心之间的距离。该样本的平方误差是该距离的平方。我们对一个簇内所有样本的平方误差求和,得到该簇的平方误差。然后对所有簇进行相同的操作,得到一次聚类分析运行结果中所有簇的最终WSSE值。
给定两个聚类结果,WSSE值较小的那个在数值上提供了更好的解决方案。然而,正如我们之前讨论的,没有数学上的“基本事实”来确定哪一组簇比另一组更正确。
此外,请注意,增加簇的数量(即K值)总是会减少WSSE。因此,应谨慎使用WSSE。只有在比较相同K值且来自同一数据集的两组聚类结果时,使用WSSE才有意义。而且,WSSE最小的那组聚类结果可能并不总是手头应用的**解决方案。同样,关于簇应该代表什么以及它们将如何使用的解释和领域知识对于确定**聚类结果至关重要。
确定K的**值始终是使用K均值时的一个大问题。有几种方法可以确定K值:
- 可视化技术:可用于检查数据集,查看样本是否存在自然分组。散点图和使用降维技术在这里对可视化数据很有用。
- 领域知识:一个好的K值取决于应用。因此,应用的领域知识可以驱动K值的选择。例如,如果你想对客户购买的产品类型进行聚类,K的自然选择可能是你提供的广泛产品类别的数量。
- 数据驱动方法:这些方法计算不同K值的某些度量,以确定K的**选择。其中一种方法是肘部法则。
肘部法则通过绘制不同K值对应的WSSE曲线来寻找“拐点”(肘部)。曲线中的弯曲处表示增加更多簇所带来的收益下降,因此曲线中的这个“肘部”为K值提供了一个建议。但请注意,肘部并非总能明确确定,尤其是在复杂数据中。
在使用K均值时,如何知道何时停止迭代?
- 标准一:当质心不再发生变化时。这意味着没有样本会改变簇分配,重新计算质心也不会导致任何变化。因此,额外的迭代不会给聚类结果带来更多变化,是时候停止了。
- 标准二:当改变簇的样本数量低于某个阈值(例如1%)时。此时,簇仅因少数样本而改变,对最终聚类结果的影响微乎其微,算法也可以在此停止。
在K均值结束时,我们得到了一组簇,每个簇都有一个质心。每个质心是该簇中所有样本的均值。你可以将质心视为该簇的代表性样本。
因此,为了解释聚类分析结果,我们可以检查簇质心。比较质心之间变量的值将揭示簇之间的差异或相似程度,并提供关于每个簇代表什么的见解。例如,如果不同客户簇的“年龄”值不同,这表明簇正在通过年龄(以及其他变量)编码不同的客户细分。
在了解了聚类的概述之后,我们现在将学习如何使用Scikit-learn和Python执行聚类。我们将使用聚类分析来生成本地气象站的天气概况模型,使用的是分钟级粒度的数据。
首先,我们导入所有必要的库并加载数据。
import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 加载数据 data = pd.read_csv('minute_weather.csv')
该数据集包含超过150万条记录,每条记录代表一分钟间隔的测量值,共有13个特征(如时间戳、气压、气温、风速等)。
我们先查看数据的基本信息和前几行。
print(data.shape) print(data.head())
由于数据集很大,为了分析方便,我们可以对其进行下采样,例如每10分钟取一个样本。
sampled_df = data[(data['rowID'] % 10) == 0] print(sampled_df.shape)
我们还可以查看数据的统计摘要,并注意到某些列(如rain_accumulation和rain_duration)有大量零值。
print(sampled_df.describe().transpose()) # 检查零值较多的列 print(sampled_df[(sampled_df['rain_accumulation'] == 0) & (sampled_df['rain_duration'] == 0)].shape)
我们将删除零值过多的列以及任何包含缺失值的行。
# 删除列 sampled_df = sampled_df.drop(columns=['rain_accumulation', 'rain_duration']) # 删除包含缺失值的行 rows_before = sampled_df.shape[0] sampled_df = sampled_df.dropna() rows_after = sampled_df.shape[0] print(f"Dropped {rows_before - rows_after} rows with null values.")
接下来,我们选择用于聚类的特征。
features = ['air_pressure', 'air_temp', 'avg_wind_direction', 'avg_wind_speed', 'max_wind_speed', 'relative_humidity'] selected_df = sampled_df[features]
由于所选特征具有不同的尺度,我们需要对它们进行标准化,以确保在聚类中具有可比性。
scaler = StandardScaler() X = scaler.fit_transform(selected_df)
fit_transform函数会计算并应用将数据缩放到标准尺度的转换。
我们的目标是创建12个簇。我们使用标准化后的数据X来拟合K均值模型。
kmeans = KMeans(n_clusters=12) model = kmeans.fit(X)
模型训练完成后,我们可以获取每个簇的中心点。
centers = model.cluster_centers_
为了便于可视化这些高维中心点,我们创建两个辅助函数:一个将中心点转换为Pandas DataFrame,另一个用于绘制平行坐标图。
def pd_centers(features, centers): col_names = list(features) col_names.append('prediction') # 将中心点转换为DataFrame Z = [np.append(A, index) for index, A in enumerate(centers)] P = pd.DataFrame(Z, columns=col_names) P['prediction'] = P['prediction'].astype(int) return P def parallel_plot(data): plt.figure(figsize=(15, 8)).gca().axes.set_ylim([-3, 3]) parallel_coordinates(data, 'prediction', color=('#FF5733', '#33FF57', '#3357FF', '#F333FF', '#33FFF6', '#FFC300', '#DAF7A6', '#FFC0CB', '#00FFFF', '#FF00FF', '#C0C0C0', '#000000')) plt.show()
现在,我们可以生成中心点的DataFrame并绘制平行坐标图。
P = pd_centers(features, centers) print(P) # 根据特定条件(如相对湿度)筛选并绘制簇 dry_days = P[P['relative_humidity'] < -0.5] parallel_plot(dry_days) warm_days = P[P['air_temp'] > 0.5] parallel_plot(warm_days) cool_days = P[(P['relative_humidity'] > 0.5) & (P['air_temp'] < 0.5)] parallel_plot(cool_days)
通过平行坐标图,我们可以比较不同簇在各个特征上的表现。例如:
- 在“干燥日”图中,红色和蓝色簇在气压、气温和风速相关特征上差异较大。
- 在“温暖日”图中,它们主要在与风相关的特征上有所不同。
- 在“凉爽日”图中,它们在平均风速和最大风速等特征上表现出差异。
通过观察这些图,我们可以尝试解释每个簇代表的天气状况。例如,某个簇可能对应于“炎热干燥且空气相对静止”的日子。

在本节课中,我们一起学习了聚类分析。我们了解到聚类是一种无监督学习方法,用于将相似的数据项组织成组或簇。我们探讨了K均值这一经典聚类算法,包括其步骤、如何选择K值、如何评估结果以及如何解释簇的含义。最后,我们通过一个实战案例,使用Python和Scikit-learn对天气数据进行了聚类分析,并利用平行坐标图可视化和解释了聚类结果。记住,聚类结果的最终意义需要通过结合领域知识进行分析和解释才能获得。

在本节课中,我们将学习回归分析,并回顾Scikit-Learn中的回归分析工具包。课程结束时,你将能够定义什么是回归,解释回归与分类的区别,并列举回归的一些应用。
在讨论回归之前,我们先回顾一下分类问题。在分类问题中,输入数据被提供给机器学习模型,任务是预测与输入数据对应的目标。目标是一个分类变量,因此分类任务是根据输入数据预测目标的类别或标签。
以下是一个我们之前见过的分类示例:输入变量是温度、相对湿度、大气压力、风速和风向等测量值。模型的任务是预测与输入数据相关的天气类别。天气类别的可能值是晴天、有风、雨天或多云。由于我们预测的是类别,这是一个分类任务。
当模型需要预测一个数值而不是类别时,任务就变成了回归问题。回归的一个例子是预测股票价格。股票价格是数值,不是类别,因此这是一个回归任务,而不是分类任务。
需要注意的是,如果你预测的不是股票的实际价格,而是股票价格会上涨还是下跌,那么这将是一个分类任务。这是分类和回归之间的主要区别:在分类中,你预测一个类别;在回归中,你预测一个数值。
以下是回归可以应用的一些场景:
- 预测第二天的最高气温。
- 估算特定区域的平均房价。
- 基于现有类似产品,确定新产品(例如一本新书)的需求。
- 预测特定智能电网的用电量。
这是一个用于预测明天最高气温的回归任务数据集可能的样子。输入变量可能是:今天的最高气温、今天的最低气温和月份。目标是明天的最高气温。模型必须为每个样本预测这个目标值。
回想一下,在监督任务中,目标值是已知的;而在无监督任务中,目标值不可用或未知。由于这里的每个样本都提供了一个数值形式的目标标签,回归任务是一个监督学习任务,与分类类似。
与分类一样,构建回归模型也涉及两个阶段:训练阶段(构建模型)和测试阶段(将模型应用于模型未见过的数据)。模型使用训练数据构建,并在测试数据上进行评估。
同样,与分类一样,构建回归模型的目标也是让模型在训练数据上表现良好,并能泛化到新数据。


我们之前讨论过的两个不同数据集的用途也适用于回归。训练数据集用于训练模型,即调整模型参数以学习输入到输出的映射。测试数据集用于评估模型在新数据或剩余数据上的性能。
我们尚未讨论但你在后续机器学习课程中会经常听到的是验证数据集。验证数据集用于确定何时停止训练,以避免模型过拟合。
在回归中,模型必须预测与输入数据对应的数值。由于为每个样本提供了目标,回归是一种监督学习任务。在回归中,目标始终是一个数值变量。
接下来,我们将讨论一种构建回归模型的具体算法。
现在我们将讨论线性回归,这是一种简单但强大且流行的回归算法。在本节结束时,你将能够描述线性回归的工作原理,讨论最小二乘法在线性回归中的应用,并定义简单线性回归和多元线性回归。
线性回归模型捕捉数值输出与输入变量之间的关系。这种关系被建模为线性关系,因此称为“线性”回归。
为了理解线性回归的工作原理,让我们看一个来自Iris花卉数据集的例子,这是一个常用的机器学习数据集。该数据集包含不同种类鸢尾花的样本及其测量值,例如花瓣宽度和花瓣长度。
这里我们有一个散点图,x轴是以厘米为单位的花瓣宽度测量值,y轴是花瓣长度测量值。假设我们想根据花瓣宽度预测花瓣长度。那么回归任务是:给定一个花瓣宽度的测量值,预测花瓣长度。
我们可以构建一个线性回归模型来捕捉输入花瓣宽度和输出花瓣长度之间的线性关系。这些样本的线性关系在图中显示为红线。
从这个例子中,我们看到线性回归的工作原理是通过样本找到**拟合的直线。这被称为回归线。在只有一个输入变量的简单情况下,回归线就是一条直线。
直线的方程是 y = m * x + b,其中 m 决定了直线的斜率,b 是截距,即直线与y轴相交的位置。m 和 b 是模型的参数。训练线性回归模型意味着调整这些参数,使回归线拟合样本。
回归线可以使用最小二乘法来确定。这个图说明了最小二乘法的工作原理:黄点是数据样本,红线是回归线,即穿过样本的直线。这条线代表模型在给定输入时对输出的预测。
每条绿线表示每个样本到回归线的距离,因此绿线代表预测值(即红色回归线的值)与样本实际值之间的误差。这个距离的平方被称为与该样本相关的残差。
最小二乘法找到使残差之和尽可能小的回归线;换句话说,我们希望找到一条线,使预测的平方误差之和最小化。
因此,线性回归的目标是使用最小二乘法找到一条通过样本的**拟合直线。
一旦回归模型建立,我们就可以用它来进行预测。例如,给定一个1.5厘米的花瓣宽度测量值,模型将根据其构建的回归线预测花瓣长度为4.5厘米。
在线性回归中,如果只有一个输入变量,则该任务被称为简单线性回归。在具有多个输入变量的情况下,则被称为多元线性回归。
线性回归捕捉数值输出与输入变量之间的线性关系。最小二乘法可用于通过找到通过样本的**拟合线来构建线性回归模型。
现在让我们切换到实际编码,看看线性回归的实际应用。
我们了解到回归允许我们预测连续变量。现在我们将使用回归技术,根据球员的属性来预测其整体表现。为此,我们将使用第一周见过的FIFA数据集进行整体分析。
在过去的七周里,你已经学到了不少知识,所以我们现在将对这个数据集进行更深入的分析。我们已经准备好使用学到的工具了。
让我们找到我们的笔记本,它名为“European S regression Analysis using Psyit Learn”,位于你本周的文件夹中,以及它需要的数据集。
打开后,我们首先导入库,然后再导入数据。快速浏览一下,我们导入了一些回归方法、pandas、SQLite(用于与关系数据库交互,这是本数据的数据源,我们将在第8周了解更多),以及其他一些数学和错误检测相关的模块。
现在我们将数据集导入到一个数据框中,然后查看这个数据框。
我们在这里做的是:连接到数据集,并使用这个连接选择球员属性,将它们加载到一个名为df的数据框中。
你可能已经注意到,一旦你习惯了数据导入,将数据导入pandas数据框总是使用类似的函数。所以,查看我们给你的其他示例笔记本,应该足以让你找到数据并将该类型数据加载到pandas数据框中,用于你的练习等。
让我们查看这个数据框的前五行,以熟悉数据。我们看到有一些与球员相关的特征:整体评分、潜力、惯用脚、进攻工作率、防守工作率等。
我们可以查看这个数据框的形状,大约有42个特征。让我们声明这些特征的列表。现在我们将选择其中一些特征,用于预测整体评分。当然,在本视频之后,你可以进一步调整这个列表并减少特征数量,以观察对预测准确性的影响。但现在,我们将使用很多这些特征来开始。
这里我们不会选择整体评分,因为那是我们的预测目标。基于从这些特征中选择的输入数据,我们将预测球员的数值整体评分。
让我们声明特征列和目标变量(命名为overall_rating)。现在开始清理数据,我们将简单地删除空值,因为我们从第一周就知道这是这个数据集的一个问题。我们使用数据框的dropna方法。
现在我们有了一个没有空值的数据切片。现在创建两个数据框,就像你现在习惯的那样:我们需要一个输入X和一个目标y。X将是我们的输入数据框,我们将选择之前声明的特征列表中的特征,加载到没有空值的数据框中。y将是目标值。
你可以随时停下来查看X和y里面有什么,以防混淆,但我们正在执行本周机器学习早期笔记本中相同的操作。
让我们看一下数据的概览,打印X的一行。我们看到X数据框中的值:潜力、传中等。我们也可以显示y,看看其中的值范围,了解存在哪些整体评分分数,我们看到它的范围大约在67到81之间。
请参考第一周的足球数据分析笔记本,对这个数据集进行进一步的探索,现在你应该能够理解那个笔记本中的很多代码了。所以,现在是时候去享受那个分析了。
我们假设你暂停了视频,可能做了一些探索,现在我将开始回归分析任务。
我们使用train_test_split执行相同的操作,将数据拆分为测试集和训练集,以便我们可以使用一个进行训练,另一个用于测试回归算法。
我们将使用两种不同的建模操作,采用不同的回归技术。首先,我们将使用线性回归器,选择特征并使用线性回归器来预测球员的整体评分。
这里我们存储一个线性回归对象,称之为regressor。这是我们从Scikit-Learn中选择的线性回归模块。然后,我们将使用该回归器,给它我们的训练输入和标签数据集(在这种情况下是数值标签,X_train和y_train)。使用回归器的fit方法,我们微调线性回归器的参数,以捕捉两个集合(X_train和y_train)之间的相互作用。我们试图拟合X_train和y_train,并创建一个模型。
然后,我们可以使用这个训练好的模型的predict方法来对测试集(即我们的X_test)进行预测。提醒一下,请注意模型从未见过这个测试集中的任何样本,所以它是在一个新的数据集上进行预测。
执行后,我们看到了y的预测值集合。如你所知,我们可以将它们与y_test中的值进行比较,看看它们有多准确。
如果我们查看并描述这个数据集的一些信息,我们看到整体平均值大约在67.6左右,最小值和最大值分别是33和94。我们看到预测的分数实际上在合理范围内。
我们也可以尝试描述预测值并进行比较,但我们将做一些不同的事情:实际上,我们使用均方根误差来衡量回归器的预测准确性。RMSE捕捉了预测值与观测值之间的差异。RMSE分数为0意味着预测完美无误差,这是理想情况,几乎从未发生。当比较两个回归模型时,RMSE较小的那个更好,因为它的预测与观测值或之前测量值的差异更小。
让我们计算一下:RMSE等于均方误差的平方根,y_true是y_test,y_pred是y_prediction(这里我们给出的参数)。当我们计算RMSE并打印出来时,我们看到线性模型给出的RMSE是2.8,这是一个好的开始。因为整体评分的范围是从33到94,平均值大约是68。
很好。现在让我们看看是否可以通过使用一个稍微复杂一点的模型来提高预测准确性。那就是决策树回归器。
决策树回归器以自顶向下的方式构建模型,通过在属性上拆分数据集。该算法选择能最大程度减少标准差的属性。你将在你的机器学习课程中了解更多,所以我在这里快速描述一下,然后继续应用它,希望这能为你即将到来的机器学习课程开胃,你将在那里学习所有这些知识。
现在让我们使用决策树回归器来捕捉球员表现与其属性之间的函数关系。fit方法再次执行微调,所以我们做的是完全相同的事情:我们有一个回归器,但由于这是一棵树,它有一个深度,最大深度是20。我们使用那个回归器来拟合训练输入和输出数据集,即X_train和y_train。我们改变了方法,但fit这一行保持不变。回归器只是另一个类和另一个方法。
完成了,我们现在有了一个模型,我们将再次使用这个模型来预测测试数据。执行后,我们看到值发生了一些变化。为了了解RMSE,我们注意到,例如,100的均方根误差会太高,因为我们的平均值是68,而我们的RMSE比我们的平均值还高。
所以让我们运行这个。再次,我们可以描述测试集,并为这个决策树回归器计算RMSE。记住,我们的线性回归操作给出的RMSE是2.8。决策树回归算法给出了一个更低的RMSE,1.44,在预测准确性方面比线性模型更好。
再次强调,RMSE捕捉了系统预测值与实际值之间的差异,因此它是衡量模型对操作表现如何的一个指标。所以,我认为对于一个目标变量平均值为68的测试集来说,1.44的RMSE是相当不错的,而且模型在预测之前从未有机会查看测试集。这确保了我们的评估是在模型未见过的数据上进行的。

我们看到线性回归模型的表现比基于决策树的回归器稍差一些。
很好,我们完成了本周(第七周)的机器学习讲座内容。我希望你会喜欢我们给你的测试笔记本,并对我们刚刚讨论的内容有更多的实践经验。

在本节课中,我们将学习关系数据模型的基本概念,并了解如何使用SQL语言从关系数据库中检索数据。课程将分为两部分:首先回顾关系数据模型的核心组件,然后通过一个具体的Jupyter Notebook示例,演示如何与SQLite数据库交互并执行查询。
在开始实际操作之前,我们先花几分钟时间回顾一下关系数据模型的基本概念以及如何与关系数据交互。
在本节结束时,你将能够描述关系数据模型的结构组件,演示构成数据模型模式的组件,解释主键和外键的用途,并描述连接操作。
关系模型的主要数据结构是表。如下图所示,这是一个用于演示的简单应用表。

你可能会注意到,表的结构与Python的DataFrame非常相似。实际上,DataFrame可以用来表示关系表,后者也被称为关系。
这个表实际上代表了一组元组。关系中的每一行就是一个元组。我们之前非正式地称其为记录,但现在我们称其为元组。因此,这个关系是一个包含六个元组的集合(即六行六列)。
根据集合的定义,它是相同类型的不同元素的集合。这意味着我不能将图中底部的红色记录作为元组添加到此关系中,因为这会引入一个重复项。在实践中,许多系统允许关系中存在重复的元组,但通常会提供机制来防止重复条目。
另一个红色元组包含了所有正确的信息,但顺序完全错误。那么,这个元组与关系中的其他六个元组相似吗?系统如何知道这个元组是不同的呢?
这让我们注意到表格的第一行,即黑色的表头。这一行是表模式的一部分。这些是我们的列,类似于Pandas DataFrame中的列。
关系表中的模式还可以指定键。第一列表明ID是一个主键。这意味着对于每个员工,ID是唯一的。知道一个员工的主键,我们就能唯一地确定该员工的其他五个属性,如first_name、last_name、department、title和salary。
现在你应该明白,一个带有主键的表在逻辑上意味着该表不能有重复记录,否则将违反与主键关联的唯一性约束。
我们将第一个表称为employees,其主键为ID。
现在,我们引入第二个表emp_salaries,其中包含员工的薪资历史记录。员工由emp_id列标识,但这些值并非随意存在,它们必须与前面employees表的ID列中的值相同。
这体现在右侧的声明中。术语“引用”意味着,此列中的值只有在被引用的employees表中出现相同的值时才能存在。被引用的表也称为父表。
在关系模型的术语中,emp_salaries表的emp_id列被称为外键,它引用了employees表的主键。请注意,emp_id不是emp_salaries表的主键,因为它有多个具有相同emp_id的元组,反映了员工在不同时间的薪资。
你可能还记得我们在Pandas中讨论过的连接操作。下图展示了一个关系连接操作的示例,该操作在employees表的前三列和emp_salaries表上进行,条件是employees.id和emp_salaries.emp_id列相等。

输出表显示了所有涉及的列,公共列只出现一次。这种形式的连接称为自然连接。
理解连接是最昂贵(即耗时和占用空间)的操作之一非常重要。随着数据量变大,表包含数亿个元组时,连接操作很容易成为大型分析应用的瓶颈。因此,对于涉及大数据的科学计算,当我们需要进行连接时,选择一个能使此操作高效运行的合适数据管理平台至关重要。
总而言之,关系模型提供了一种描述数据记录之间唯一关系(包括记录标识符之间的关系)的方法。Pandas的DataFrame实现了关系数据模型的一些特性,使得处理关系数据库更加容易。
在简要回顾了关系模型之后,我们现在来讨论如何从关系数据库中检索数据。
在本节结束时,你将能够描述数据检索的含义,解释SQL的用途,并创建简单的SELECT查询。
数据检索指的是用户指定所需数据并从数据源获取数据的方式。在本课程中,我们以两种方式使用“数据检索”这个术语。
假设你的数据存储在一个遵循特定数据模型(例如关系数据模型)的数据存储中。通过数据检索,我们将指代你如何指定从关系数据存储中获取所需数据的方式,以及数据管理系统内部为计算或评估该指定检索请求而进行的处理。
现在,让我们看看如何使用一种称为结构化查询语言或SQL的特定语言进行查询规范。
SQL是处理结构化数据时无处不在的查询语言,但它已经以多种方式扩展以适应其他类型的数据。在本课中,我们将坚持使用该语言的结构化方面。
你应该知道,SQL既用于Oracle等经典数据库管理系统,也用于Spark等现代分布式大数据系统(以Spark SQL的形式)。
现在让我们用一个示例来工作。我们为此业务设计的模式包含三个关系(或表)。
以下是这些表:
- 第一个表列出了酒吧。它包含酒吧的名称、地址和许可证号。请注意,名为
name的属性带有下划线,因为它是bars关系的主键。回想一下,主键指的是一组属性(在本例中仅为name),它们使记录唯一。 - 第二个表称为
beers,列出了啤酒的名称和制造商。 - 第三个表是
sells表。并非每个酒吧都销售相同品牌的啤酒,即使销售,同一产品也可能因经营成本不同而有不同的价格。因此,sells表记录了哪个酒吧以什么价格销售哪种啤酒。
SQL查询最基本的结构是SELECT ... FROM ... WHERE子句。
在这个示例中,我们正在查找由“Heineken”制造的啤酒名称。因此,我们需要指定:
- 输出属性:在本例中是啤酒的
name。 - 用于回答查询的逻辑表:在本例中是
beers。 - 所有所需数据项应满足的条件:即名为
manf的属性值等于“Heineken”。
我们的查询是:
SELECT name FROM beers WHERE manf = 'Heineken';
需要注意以下几点:
- 字面值“Heineken”用单引号括起来,因为它是一个字符串字面量。在这种情况下,字符串应该完全匹配,包括大小写。
- 回顾我们在Pandas中讨论过的数据操作,你会认识到这种形式的查询也可以表示为对
beers关系的一个选择操作(条件是manf属性),然后是一个投影操作(从选择操作的结果中仅输出name属性)。因此,选择操作找到beers中制造商为“Heineken”的所有元组,然后从这些元组中仅投影name列。
此查询的结果是一个具有单个属性name的表。在Pandas中,这将是一个列或一个Series对象。
我们使用另外两个示例查询来说明SQL的更多特性。
第一个查询查找昂贵的啤酒及其价格:
SELECT DISTINCT beer, price FROM sells WHERE price > 15;
假设我们认为价格超过15美元一瓶的啤酒是昂贵的。从模式中我们知道,价格信息来自名为sells的表,因此FROM子句应使用sells。WHERE子句很直观,指定价格大于15。
现在请注意,sells关系还有一个bar列。如果两个不同的酒吧以相同的价格销售同一种啤酒,我们将在结果中得到两个条目,但这并不是我们想要的。无论有多少酒吧以相同价格销售同一种啤酒,我们只希望结果出现一次。这是通过第一行中的SELECT DISTINCT语句实现的,它确保结果关系没有重复项。
第二个示例展示了结果必须满足多个条件的情况:
SELECT * FROM bars WHERE addr = 'San Diego' AND license LIKE '32%';
在此查询中,业务必须在圣地亚哥,同时必须是临时许可证持有者(即许可证号应以“32”开头)。如图所示,这些条件通过AND运算符组合在一起。因此,此查询将选择表中的第三条记录,因为前两条记录满足第一个条件但不满足第二个条件。
如果我们的数据库很大,而我们只需要返回五个结果(例如,用于显示的样本),我们可以使用LIMIT子句:
SELECT * FROM bars WHERE addr = 'San Diego' LIMIT 5;
LIMIT子句的确切语法可能因数据库管理系统供应商或Python环境而异。
总而言之,SQL是结构化关系数据的标准查询语言,它类似于Pandas DataFrame,尽管它可以提供更多操作。除了我们在本视频中重点介绍的简单数据选择查询外,SQL还允许进行连接和其他操作,但我们不会深入探讨这些操作的细节。
接下来,我们将在一个Jupyter Notebook中向你展示如何在Python环境中使用这些选择查询。
在本笔记本中,我们将使用一个名为Iris的流行数据库。它包含了150个鸢尾花样本及其被分为三个物种的分类信息。
我们将使用Kaggle上SQLite格式的Iris数据库来完成本笔记本的其余部分。
如果你能在你的week8文件夹中找到这个笔记本,它名为Work_with_Database.ipynb。笔记本顶部有一个Kaggle链接:kaggle.com/uciml/iris。你需要从这个链接下载数据,数据文件名为database.sqlite。它应该放置在你为week8准备的data/iris文件夹中。
现在请暂停视频,前往此链接并下载数据集。
假设你已暂停视频并下载了数据集,在我们继续之前,请运行接下来的两个代码单元格,以检查你是否成功完成了此步骤。
以下是接下来两个单元格的内容:
import os data_iris_folder_contents = os.listdir('data/iris') assert 'database.sqlite' in data_iris_folder_contents, 'Please download the database.sqlite file and place it in the data/iris folder.'
第一个单元格导入os模块,并将data/iris目录的内容加载到名为data_iris_folder_contents的对象中。错误消息是一个字符串,如果文件不存在或数据库文件不在该文件夹中,我们将显示该消息。我们使用assert语句进行检查。assert语句通常是通过创建此类内置测试来定位或识别Python程序中错误的好方法。如果你进行大量测试,实际上会使用很多assert语句。
如果运行成功,我们就可以继续与数据库交互了。
我们将首先导入sqlite3。sqlite3是一个Python模块,允许我们执行简单的SQL操作。我们将使用它来连接我们放置在iris文件夹中的数据库文件。
import sqlite3 connection = sqlite3.connect('data/iris/database.sqlite') cursor = connection.cursor()
现在我们有了一个连接对象。我们将使用这个连接对象来获取一个游标对象。这个游标对象实际上是我们与数据库交互的接口。
在继续之前,我还想提一下,SQLite随标准Python一起提供,因此它不是你需要单独安装的库,这是一种与简单数据库查询交互的便捷方式。
让我们看看游标对象的类型:
type(cursor)
输出是sqlite3.Cursor。正如我提到的,这是我们与数据库交互的接口,主要通过游标对象的execute方法,我们能够在已连接的数据库上运行任何SQL查询。
例如,我们可以获取数据库中保存的所有表的列表。这是通过从sqlite_master元数据表中读取name列来完成的。
cursor.execute("SELECT name FROM sqlite_master") for row in cursor: print(row)
此执行的输出将转换为一个迭代器。如果我直接运行cursor.execute(‘SELECT ...’),我会得到一个迭代器。现在我正在for循环中使用该迭代器来打印每一行。
在Iris数据库的情况下,我们只有一个表,所以你会看到它在这里被称为Iris。
直接执行查询并收集结果的快捷方式也称为fetchall方法。因此,我们可以使用SELECT查询从特定表检索数据,并一次性获取所有结果,而无需使用for循环。
cursor.execute("SELECT * FROM Iris") results = cursor.fetchall() print(type(results))
我们应该得到一个列表。如果我们打印这个结果的类型,我们会看到它是list类。我们可以处理这个列表,但这种方式非常低级且效率不高。
还记得我们在动手操作环节之前的视频中提到的LIMIT子句吗?最后一个查询语句,比如SELECT * FROM Iris LIMIT 3,会给我们带来前三行。你可以将其更改为5、20或任意行数并执行。
cursor.execute("SELECT * FROM Iris LIMIT 20") sample_data = cursor.fetchall() for row in sample_data: print(row)
现在我们可以看到列表中所有的20行数据,这个列表是fetchall的结果。我们知道如何处理这种数据结构,对吧?我们得到了这个列表的列。
虽然可以使用SQLite创建许多这样的操作,但正如你所知,Pandas提供了一种更直观、更高效的方式来与数据交互。就像我们之前处理欧洲足球数据库一样,我们现在将使用Pandas的read_sql_query函数将Iris表中的数据加载到Pandas DataFrame中。
你以前见过这个,但现在让我们结合对SQLite的了解再回顾一下这里发生了什么。
import pandas as pd iris_data = pd.read_sql_query("SELECT * FROM Iris", connection) print(iris_data.head()) print(iris_data.dtypes)
我们导入了pandas as pd。在这里,我们创建了一个名为iris_data的DataFrame对象。我们使用pandas的read_sql_query方法,并给它一个SQL查询(SELECT * FROM Iris)和连接对象。通过这个连接令牌和查询,pandas的read_sql_query函数知道如何获取该查询的结果并为我们创建一个DataFrame。
运行这些代码后,我们可以使用head操作查看前五行,也可以查看每列的数据类型。现在使用pandas处理数据就很简单了。

我们还可以创建更复杂的查询来限制加载到DataFrame中的数据。因为如果数据很大,表可能有很多行和列,而我们可能只想选取其中的一部分。现在你了解了这些SELECT查询,实际上可以使用SQL查询来创建约束。
例如,下面的查询与之前完全相同,但它只选择Iris-setosa物种,而不是所有三个物种。
query = "SELECT * FROM Iris WHERE species = 'Iris-setosa'" iris_setosa = pd.read_sql_query(query, connection) print(iris_setosa.shape) print(iris_data.shape)
我们有一个查询说:SELECT * FROM Iris WHERE species = 'Iris-setosa'。执行后,我们会看到结果DataFrame中只有一个物种。如果我们查看iris_setosa.shape和之前iris_data.shape的对比,我们会发现iris_setosa只有50行,而iris_data有150行。在Iris数据库中,每个物种有50个样本,总共150行。通过这个新查询,我们能够以关系查询的方式“切出”属于Iris-setosa物种的50个样本。
虽然我们可以扩展这些SQL查询,但为了简单起见,我们在此停止。和往常一样,我鼓励你使用这个数据库为自己构建练习。例如,使用Iris数据库和我们上周讨论的机器学习库来构建模型。事实上,Iris数据库通常用于演示不同的机器学习算法。因此,现在是介绍这个数据集的好时机,希望你能用它来为自己创建练习。
在本节课中,我们一起学习了关系数据模型的核心概念,包括表、元组、模式、主键、外键以及连接操作。我们还介绍了如何使用SQL语言从关系数据库中检索数据,学习了基本的SELECT ... FROM ... WHERE查询结构,并通过DISTINCT、AND、LIKE和LIMIT等关键字增强了查询功能。最后,我们在Jupyter Notebook中实践了如何使用Python的sqlite3模块和Pandas库与SQLite数据库进行交互,执行查询并将结果加载到易于处理的DataFrame中。这些技能是进行数据科学分析和处理结构化数据的基础。

在本节课中,我们将学习如何使用一个名为NLTK的流行Python包进行自然语言处理。
自然语言处理,简称NLP,是一个数据科学术语,指计算机与人类使用的自然语言之间的交互。这是一项不简单的任务,因为人类语言具有歧义性。作为人类,我们擅长理解话语的上下文,并将其与我们周围概念的理解联系起来。然而,以算法方式实现这一点并不简单。NLP领域致力于改进和发展算法及数据技术,以有效且快速的方式实现这一目标。
你可能已经使用过NLP应用,例如在线新闻或书籍摘要、搜索最热门Twitter话题的关键词,或者使用手机上的虚拟助手。让我们列举一些这类应用。
以下是NLP技术的一些应用实例:
- 语音识别引擎:如Siri、Google Now或Alexa。这些引擎旨在学习人类说话的内容和方式,并不断提高其准确性。
- 自动翻译器:如Google Translate或Facebook的自动状态翻译。它们使用NLP,并采用了一些近期非常有效的基于神经网络的技术,不仅考虑单词和短语,还通过查看待翻译文本周围的单词来考虑上下文。
- 聊天机器人:能够通过Facebook Messenger回答问题的聊天机器人是NLP的另一个例子。它们使用NLP引擎来处理问题,通常只是对问题进行简单分类,并将其与现有答案进行匹配。
在本课中,我们将通过一个使用NLTK进行自然语言处理的笔记本来学习。NLTK是最流行的用于NLP的Python包。它是一个开源库,提供了用于导入、清理、预处理人类语言文本数据的模块,然后对这些数据集应用计算语言学算法或机器学习算法(如情感分析)。它还提供了超过50个数据集供我们开始使用,包括我们将在示例笔记本中使用的电影数据库。
让我们开始使用我们的笔记本。
上一节我们介绍了NLP的基本概念和NLTK库。本节中,我们来看看NLTK提供的数据集,即语料库。
NLP技术依赖于大量的文本或其他语言数据。这些数字化的集合统称为语料库。你会听到的另一个词是corpus,它是corpora的单数形式。
正如你在我们简短的实时会话中所见,NLTK提供了下载其中一些大型数据集的方法。导入NLTK后,你能够使用nltk.download()与所有这些数据集的下载界面进行交互。更具体地说,我们下载了电影评论语料库以开始处理我们的笔记本。
电影评论语料库已下载到你的主文件夹中。因此,如果你列出此文件夹下的目录,你会看到该语料库有2000条评论,其中一半是正面的,另一半是负面的。这些文件或目录中的每条评论都有大量的文本,不仅仅是关于电影的简短意见,平均约800个单词。
现在让我们切换到我们的笔记本,查看其中一些评论。
让我们从上次停止的地方继续,导入我们下载的电影评论。
from nltk.corpus import movie_reviews

NLTK语料库中所有数据集提供的fileids方法可以访问所有可用文件的列表。我们可以使用len函数查看此列表的长度,但首先让我们找出从语料库中获得的所有文件。也许是前五个文件,它们位于负面的neg文件夹中。我们看到文件名和底部的五个文件。但这仍然是一个列表。所以我们可以实际运行长度操作。对于这两个列表都是如此。
因此,如果我们想获取电影评论的长度,即movie_reviews通过fileids提供给我们的所有评论,我们可以这样做。
当我们查看负面文件和正面文件时,我们看到它从负面评论开始,然后切换到pos目录中的正面评论,因此它显示我们有两个目录,每个目录有1000条评论。1000条是负面评论,1000条是正面评论。在这里的第7行,我们所做的是将列表过滤为正面和负面类别。我们看到每个类别有两个相等的列表。

因此,我们也可以使用movie_reviews的raw方法检查其中一条评论。每个文件被分割成句子,数据的策划者还从每条评论中删除了任何对电影评分的直接提及。
在下一个代码行中,我们获取了第一个正面评论。这是评论。它确实有大量的文本,但我们如何开始处理这些文本呢?一种方法是将此文本进行分词。在下一个视频中,我们将讨论这种对任何类似文本进行分词的技术,并将其用于自然语言处理。
现在让我们谈谈文本中的单词分词。在本视频结束时,你应该能够解释分词的含义并使用NLTK单词分词器。
NLP的第一步通常是将文本分割成单词。这个过程可能看起来简单,但要处理所有边缘情况非常繁琐。什么是边缘情况?它们包括标点符号的不一致使用、缩写或单词的缩短形式,也可能包括像“New York-based”这样的包含连字符的单词示例。我们如何对此类情况进行分词?NLTK提供了库来应对这些挑战。
当我们稍后切换到笔记本时,我们将首先使用一个简单的基于空格的分词器。然后我们将学习如何使用NLTK更好、更轻松地完成分词。
现在切换回我们的笔记本。对于我们的分词示例,让我们使用罗密欧的简短文本。我们看到这里的示例有一些标点符号。让我们看看这个罗密欧文本。这里有一些标点符号,比如“love”后面的感叹号,我们还有例如一个带连字符的“well-seeming”。如果我们现在在Python的下一行中使用字符串分割函数,它将给我们这些单词的列表。让我们执行它。哦,抱歉,我没有运行前一行,别忘了逐行运行代码。在第12行或代码单元格12之后,我们将使用这个罗密欧文本来分割。检查一下这个。我们看到有一些标点符号与单词连在一起,比如“love!”或“hate.”,以及像“well-seeming”这样的组合词。它们都被列为一个单词。理想情况下,我们希望“love”是一个单词,那个标点符号感叹号是另一个单词,或者我们甚至可能删除它,但我们如何删除这些标点符号呢?对于这个任务,我们需要下载一个已经训练好的英语分词器。那个分词器叫做punkt。所以它来处理标点符号。让我们执行这个代码单元格。
然后我们可以使用这个单词分词器来生成标记。记住,之前我们实际上只使用了分割操作,romeo_text.split(),这是字符串提供给我们的。我们正在尝试做更多,我们实际上正在尝试使用NLTK的punkt,它已经定义了这些标点符号,并且我们正在使用其中的word_tokenize来生成一个单词列表。现在如果你显示这些单词列表,我们确实看到上面有的“love!”被很好地分开了。

好消息是,NLTK中的所有语料库已经提供了一种为每个数据文件生成分词后单词的方法。

所以对于我们的电影数据库,让我们去找找看。第一个正面文件的单词可以使用这里的代码块访问。我们有movie_reviews.words()和positive_fileids[0]作为我们的第一条记录。让我们执行这个。我们这里有一个列表,对吧?我们拥有来自第一条正面记录的单词列表。现在我们有了单词,在接下来的系列视频中,让我们看看如何使用这些单词为它们构建一个简单的词袋模型。
在本视频中,我们将回顾词袋的含义。在本视频结束时,你应该能够解释词袋的含义,理解如何从单词构建机器学习特征,并举例说明停用词。
词袋模型是将文本主体表示为松散单词集合的一种非常简单的方式。它将其扁平化为一个无序的单词集合。尽管它忽略了与单词相关的句子结构,但这种简单的技术对于识别文本中的主题或情感非常有用,例如产品评论是负面还是正面情感,或者文本主体谈论什么。
我们可以在特征矩阵中使用这些单词,其中每个单词是一列,每个文本主体或我们电影示例中的每条评论是一行,具有布尔数据值。评论行中的一个单元格被分配为True,如果该单词出现在评论中;如果未出现,则分配为False。
仅通过查看这个具有有限单词集的矩阵中的这三行,我们就可以识别出这些评论的主题是电影。并且可能评论1和评论3是正面的,评论2是负面的。
在我们继续到笔记本之前,我想提一下,在进一步分析之前,通常的做法是从词袋中过滤掉停用词,甚至可能过滤掉标点符号。停用词是像“the”、“a”、“and”、“is”这样的词,它们出现频率很高,但在识别被处理文本的上下文方面没有太大意义。
现在让我们切换回我们的笔记本,执行我们的第一个词袋模型。
正如我们刚刚概述的,从词袋模型中,我们可以构建供分类器使用的特征,这里我们假设每个单词是一个可以是真或假的特征。我们在Python中将其实现为一个字典。但是,句子中的每个单词,我们将其与True关联。如果一个单词缺失,那将等同于分配False,或者在这种情况下,我们不会有任何东西。所以让我们为romeo_words中的每个单词执行这个循环。正如你在这里看到的,一个字典,其中romeo_words中的每个单词都被分配了True,并且没有False值,因为我们没有为任何东西分配False。
你会记得我们早期的视频,这里的下划线是进入标准输出的最后一个输出。所以如果你已经将该字典分配给了一个变量,你需要在此单元格中写入该变量的名称。为了概括我们所做的,这里的字典创建,我们可以将我们在这里所做的转化为一个Python函数。
在这个代码单元格中,我们定义了一个Python函数。接下来我们将使用它。让我们执行这个Python函数,它叫做build_bag_of_words_features,所以我们正在为我们刚刚创建的单词字典构建词袋特征。所以这个函数将接受一组单词并返回其字典。让我们运行该函数,看看是否得到与刚才第20行相同的输出。很好,我们得到了相同的输出。这就是我们想要的。
但是,请注意,这里像感叹号、逗号或点这样的标点符号仍然显示出来,这些对于分类目的是无用的。同样,我们有像“of”、“that”、“is”或“in”这样的词。这些不需要被包含。所以这些词被称为停用词,NLTK实际上为每种语言(在这种情况下是英语)提供了一个语料库,所以我们可以实际从NLTK下载停用词。
我们现在可以在英语中使用停用词,以及string类中的标点字符。这些是列在我们第24行输出中的字符。我们可以使用它来创建一个我们不想要的列表。我们将这个列表称为useless_words。这里我将useless_words创建为一个列表,它是英语停用词和标点字符的组合。
如果你有好奇心,或者不完全理解停用词是什么,请随时打印出这里的停用词,nltk.corpus.stopwords.words('english'),稍微探索一下这些词,看看它们是什么。当然,我们现在可以打印useless_words。自己看看这个列表,所以这里有“i”、“me”、“my”、“myself”,列表从那里继续。为简单起见,我将注释掉那一行,然后再次运行。
现在我们将实际更新我们构建的词袋函数,添加一个if语句来检查单词是否存在于useless_words中,如果存在则跳过该单词。所以我们有相同的函数。之前是True,现在是1。对于words中的每个word,但我们有一个条件,if not word in useless_words,所以这意味着如果单词不在useless_words中,则将其添加到我们的字典中。
正如你可能已经注意到的,这里我们没有使用True作为单词的值,而是使用1在字典函数中。让我们运行我们的函数。现在我们将使用这个函数build_bag_of_words_features_filtered,特征已经过滤掉了无用词。给它romeo_words作为输入。如果你这样做,我们看到标点符号和我们之前有的一些停用词消失了,我们在字典中有了一个更清晰的单词列表。
我们正在取得进展。接下来,让我们看看如何计算和绘制此列表中单词的频率。
现在让我们看看我们可以进一步做些什么来分析词频。在本视频结束时,你应该能够计算一个项目在列表中出现的次数,在对数坐标轴上绘制词频,并绘制词数直方图。
当我们切换到笔记本时,我们将看到,快速检查我们电影评论数据库中的单词数量显示有160万个单词,通过移除我们所谓的无用词,可以减少到71万个。这仍然有很多单词。你如何找出每个单词的频率,即每个单词在这个语料库中出现了多少次?我们将为此目的使用Python中collections包的Counter对象。
Counter的工作方式与我们之前讨论过的unique命令非常相似。我们可以向它提供一个单词列表,它返回一个对象,通过该对象我们可以找出每个单词的重复次数。你可能已经猜到“movie”是电影数据库中一个非常频繁的单词,实际上它在这个语料库中出现了5771次。
一旦我们在计数器中有每个单词的频率,我们将看到如何使用matplotlib绘制单词的分布。这个图表是从我们的笔记本中复制的,我们排序了单词计数,并在对数坐标轴上绘制了它们的值,以检查分布的形状。这种可视化在比较两个或多个数据集时特别有用。较平坦的分布表示词汇量大,而峰值分布表示词汇量有限,通常是由于主题集中或语言专业化。
我们还将创建单词的直方图以可视化频率。
现在让我们切换到我们的笔记本,看看我们刚刚回顾的内容的实际操作。
我们现在开始计算电影评论语料库中单词的频率。让我们转到我们的笔记本,首先使用len函数快速检查单词数量。我们之前提到过,我们可以获取电影评论中的所有单词并将其分配给all_words。我们将对这些all_words使用len函数,并以百万格式打印。我们在非常早期的视频中回顾过其中一些,希望你仍然记得。所以我们在这里做的是将这个数字转换为百万格式。所以我们有大约160万个单词,这是一个很大的数字,但这个数字包括我们之前列出的无用词。
让我们在这里过滤掉那些单词。使用一个for循环。并将此结果或结果列表称为filtered_words。所以我们要做的是,我们将分配filtered_words,这个for循环的输出。它说word for word in movie_reviews.words(),所以对于电影评论中的每个单词,仅当单词不在useless_words中时才执行此操作。所以我们可以执行这个。如果你想查看filtered_words的类型,让我们再次执行这个。需要一点时间,因为它不断检查单词是否在列表中。我们看到filtered_words确实是一个列表。我们将得到此列表中的单词数量。现在下降到大约71万个,即0.71百万,所以大约是原来的一半。我们能够将单词数量减少到大约一半。
接下来,让我们使用这些过滤后的单词创建一个计数器对象。我们要做的是,首先从collections导入Counter。然后我们将给Counter这个filtered_words,即大约71万个单词。并将其转换为一个计数器对象,我们在这里初始化的这个Counter类将创建一个单词计数器对象。所以我运行这个,一旦我们有了这个单词计数器对象,它就成功运行了,正如你看到的,速度很快。
我们可以使用Counter类提供给我们的任何函数或此类的属性。计数器对象的most_common函数让我们看到电影数据库中最频繁的单词。让我们实际执行那个,在下一个单元格中,我将打印这些最常见的单词,前10个,正如你在这里看到的。正如我们在这里看到的,最常见的单词是我们电影数据库中频率最高的单词,正如预期的那样,是“film”、“one”和“movie”。
现在让我们绘制这个词频计数器。我们需要在绘制之前对这个列表进行排序。我们将使用matplotlib,正如你之前见过的matplotlib,你已经熟悉了。我们将使用它来生成列表的对数图。所以我们有排序后的word_counter及其中的值,我们将其分配给一个名为sorted_word_counts的列表。将这些sorted_word_counts提供给loglog函数以创建那个对数图,我们将命名我们的x轴标签为“word rank”,y轴标签为“frequency”。所以如果我们这样做,我们将得到之前在幻灯片中概述的那个图表。这是一个对数图。正如我们在这里描述的,这种较平坦的分布表示词汇量大,就像我们拥有的那样。
我们还可以绘制排序后单词计数的直方图,它显示有多少单词具有特定范围内的计数,不是每个单词,而是将具有相似计数的单词分箱在一起。所以,由于我们的语料库中有许多低频词,我们会看到直方图在这些低计数(接近0)处达到峰值,我们有一个巨大的单词峰值,然后逐渐减少到5000左右的数量级。在这种情况下,以对数刻度显示这将为我们提供一个更好的图表来传达此信息,所以我们要做的是,在直方图中,对于值计数,我们将在这里说log=True。同样的50个分箱。我们将得到一个更好、信息更丰富的图表。
随着你在这个专业课程中学习概率和其他课程,你将了解更多关于它的知识。
很好,我们几乎到了笔记本的结尾。像往常一样,我们把最好的留到最后,那就是将我们刚刚进行的探索性分析用于分类模型。让我们在接下来的系列视频中完成这个,看看我们如何实现它。
我们现在将开始使用我们创建的词袋模型,在电影评论的情感分析分类器中。在本视频结束时,你应该能够解释什么是情感分析,使用NLTK训练一个情感分析分类器,并检查此模型在训练和测试数据上的准确性。
那么什么是情感分析?该术语指的是识别编码在文本主体(如产品评论或文学作品)中的态度或情感的活动。使用机器学习进行分类是用于情感模型的一种技术。在我们的笔记本中,我们将使用电影评论语料库构建一个情感预测器。
正如你所记得的,分类是一项监督活动,需要来自真实数据的标签。这就是我们将利用词袋和我们下载的经过整理的负面和正面评论的地方。我们将使用之前实现的词袋函数为每条评论创建正面或负面标签。我们将为此任务使用朴素贝叶斯分类器。
虽然我们之前没有回顾过朴素贝叶斯,但它是一个非常简单的分类器,采用概率方法进行分类。这意味着输入特征和类别标签之间的关系被表示为概率。因此,给定样本的输入特征,估计每个类别的概率,然后具有最高概率的类别确定样本的标签。
现在让我们最后一次切换到我们的笔记本,以使用NLTK的朴素贝叶斯(这次不是scikit-learn)进行情感分类任务来结束。
我们现在将开始为分类器准备输入和标签数据集。所以我们再次使用朴素贝叶斯分类器。我们很幸运,数据库经过整理,将正面和负面评论分开,所以我们将以此作为真实数据,并为正面和负面评论构建两个词袋字典。
所以我将在这里快速完成这个。我们有负面特征build_bag_of_words_features_filtered。这是我们之前创建的函数。我们正在使用它,并给出负面文件ID的单词。我们将有一个标签“neg”与之关联。对于negative_fileids中的每个文件,我们将构建一个词袋并将其存储在negative_features中。所以我们可以打印negative_features中的一条记录。我们将看到对于一个评论有一堆单词,并且它被标记为负面。
我们可以对正面特征做同样的事情,并打印一条正面特征的记录。我们现在可以看到,例如,positive_features[6],该数据框上的第六条评论是正面的。
所以现在我们有了我们的两个特征,记住我们每个里面有1000条记录。所以对于这些,我们将有大约1000条带有正面标签的记录,以及大约正好1000条带有负面标签的记录。所以现在我们将开始使用朴素贝叶斯分类器。让我们从这里导入,正如你看到的,这不是scikit-learn,这是NLTK分类器,所以它是NLTK附带的分类器。
记住我们在每个特征中有1000条记录,我们可以使用80%的数据在朴素贝叶斯中进行分类。当我们提供每个特征的前800行时,即80%,10乘以80%等于800,所以我们将第800个存储在名为split的变量中。我们将使用该分割来切片前800个用于训练,剩余的200个用于稍后的测试。
所以我们在这里做的是,我们给朴素贝叶斯分类器来构建一个我们将称为sentiment_classifier的分类器。我们正在使用朴素贝叶斯分类器来训练它。使用前800个正面特征和前800个负面特征,记住,它们有标签“pos”和“neg”,我们像这里看到的那样创建了它们。所以分类器将知道如何处理它。
所以我们创建了情感分类器。我们需要再次创建分割,别忘了在继续之前运行每个单元格,我们将使用正面和负面评论的前800条记录,使用朴素贝叶斯分类器创建我们的分类器,我们将其称为情感分类器。
所以我们现在将检查我们构建的模型的准确性,这个模型被称为情感分类器,在训练集上。我们将使用分类准确性函数,它是分类的实用函数。我们给它分类器以及用来训练它的数据,即正面和负面特征。记住,我们训练了它,模型已经看到了这个数据集,这些是训练数据集。所以你会期望什么样的准确性?它应该很高,对吧?因为模型已经见过这些数据。我们看到它大约是98%的准确性,所以很好,这是正常的。但是模型在它尚未见过的20%数据上表现如何?让我们继续,现在将那20%提供给准确性函数,我们正在应用情感分类器来预测其余数据的标签。所以这些是我们的测试数据。如果我们计算它的准确性,大约是71%。人类的估计准确性约为80%,因此对于这样一个简单模型来说,约70%的准确性是相当不错的,再次考虑到人类的估计准确性约为80%。
记住,我们有一个很大的词汇量,情感分类器使用了所有的单词。但是哪些单词给了我们这种较高的准确性?我们构建的情感分类器模型有一个函数叫做show_most_informative_features,你实际上可以运行它,看看哪些单词或哪些评论中的特征信息量最大。如果你看这个输出,像“outstanding”、“insulting”、“vulnerable”这些词实际上对结果影响很大。
所以我们在这里停止,让我们清理一下我们的笔记本。

在本节课中,我们一起学习了自然语言处理的基础知识,并实际使用了NLTK库。我们首先了解了NLP的概念及其在日常生活中的应用。然后,我们探索了NLTK提供的语料库,特别是电影评论数据集。接着,我们学习了文本分词的重要性以及如何使用NLTK的分词器。之后,我们介绍了词袋模型,这是一种将文本表示为特征向量的简单而有效的方法,并学习了如何过滤停用词。我们还进行了词频分析,使用Counter对象和matplotlib来可视化单词的分布。最后,我们应用所学知识,构建了一个朴素贝叶斯分类器来进行电影评论的情感分析,并评估了其性能。通过这一系列步骤,你掌握了使用Python和NLTK进行基础自然语言处理任务的完整流程。

在本节课中,我们将介绍课程的最终项目,并预览两个高级数据分析示例。最终项目要求你综合运用所学知识,独立完成一个完整的数据分析流程。此外,我们还将了解两个由专家和学生开发的示例笔记本,它们展示了如何将Python数据科学工具应用于天体物理学和生物信息学领域。
首先,祝贺你在课程中取得的成就。你已经学习了大量关于数据分析的知识。
接下来,我们将把所有学习内容整合到一个最终项目中。我们为这个项目预留了充足的时间,希望你能够创作出令自己自豪的作品,并向朋友和家人展示你在完成本课程后所具备的能力。
还记得课程开始时展示的这张幻灯片吗?这是我们为你设定的课程目标。到课程结束时,你应该能够:找到有用的数据集、围绕数据提出研究问题、执行基本的数据分析以帮助回答研究问题,并展示你的发现。
在这个最终项目中,你将完整地实践这一流程。具体来说,你需要从零开始创建一个Jupyter笔记本,并对你选择的数据集进行数据分析。
接下来我将从宏观层面概述这个项目,但在开始之前,请务必仔细阅读项目描述和评分标准。
本周,你需要找到一个自己感兴趣的数据集。我们将在后续的阅读材料中提供一个包含有价值数据的网站列表,但你可以自由选择任何数据。如果你有与个人爱好或工作相关的公开数据,也可以使用。
以下是项目第一阶段的主要步骤:
- 探索数据:你需要在Jupyter笔记本中探索数据,了解其内容。这个过程通常也涉及数据清洗。
- 提出研究问题:在探索过程中,你应该思考哪些问题是可以通过数据回答的。你需要确定一个具体的研究问题:你想知道什么,而数据可能帮助你回答什么?
到本周结束时,你应该准备好用于下周分析的数据集和研究问题。
在第十周,你将基于选定的数据继续工作。虽然探索性数据分析永远不会停止,但我们将主要专注于运用已学的建模方法进行深入分析。
以下是项目第二阶段的主要步骤:
- 数据处理:根据数据的格式,你可能需要进行数据清洗或合并。
- 应用技术:计划使用你在课程后期学到的机器学习和文本分析技术。
- 多角度审视:从多个角度审视数据以构建答案。检查数据是否合理,如果适用,你执行了哪些检查来验证数据的准确性?
- 数据可视化:通过可视化更好地理解数据。
- 批判性思考:当你开始获得答案的洞察时,要保持怀疑态度。一个对你的结果持批评态度的人会提出什么问题?如果可能,尝试回答这些批评性问题,或者坦诚地承认你的结果可能因为基于某些假设而存在局限性。
- 整理与呈现:当你对结果感到满意,并理解并记录了其局限性或支持性发现后,你将整理一份演示文稿,供其他学习者审阅并提供反馈。我们会提供一个幻灯片模板供你整理发现。你的责任是使用你的分析来支持结论,并以清晰简洁的方式传达你的发现。
我鼓励你让朋友或家人对你的演示文稿提供初步反馈。对你来说清晰的内容,对他人未必如此。
一旦你对演示文稿和笔记本感到满意,就可以提交进行同行评审。
说到同行评审,我需要快速提醒你认真对待同行评审的重要性。就像在第六周一样,你需要保持批判性但公平。
我通常发现,人们在第一次进行同行评审时,往往会比必要的更苛刻。因此,如果你需要权衡,倾向于宽松一些。这并不意味着给一个糟糕的项目打高分,而是意味着,如果你在“良好”和“优秀”评级之间犹豫,可以倾向于给出更高的分数。
由于这个项目是开放式的,其他学习者选择的主题可能与你不同。例如,你可能是一位热心的环保主义者,而你发现自己正在审阅一个试图根据地理调查预测北极下一个石油钻探地点的笔记本。你可能不喜欢作者选择的主题,但你的工作不是评判他们的主题选择。你的工作是评判他们的工作。他们是否很好地分析了钻探地点?他们的预测是否准确?他们的方法是否恰当?例如,他们是否通过分离训练集和测试集来使用公认的机器学习方法?
换句话说,尽你所能客观地评审这项工作。

这个项目是开放式的,我们这样设计的部分原因是希望你能完全投入到你的数据中,并且我们知道,如果你关心主题和数据,你更有可能做到这一点。
因此,请进行一次仔细而有意义的分析,同时也试着享受这个过程。我们迫不及待想看到你的创作。
本周,除了项目之外,我们还想为你提供一些示例笔记本,这些笔记本展示了如何将各种Python数据科学库应用于现实生活中的科学应用。
一个关于天体物理学的应用是由我们的课程助理Andrea Zonka博士开发的。Andrea拥有米兰大学的天体物理学博士学位,现在在圣地亚哥超级计算机中心担任高级数据科学家和高性能计算专家。他还作为软件技能培训的一部分教授计算数据科学的实践课程。软件技能培训是一个非营利性基金会,为早期职业科学家教授计算技能,这对你来说可能是一个很好的资源。
你可以在week9目录下找到Andrea开发的这个笔记本,文件名为Plan satellite data simulation using pandas。这个示例的灵感来自Andrea在圣地亚哥超级计算机中心的一个项目,在该项目中,一个科学家合作小组分析了宇宙微波背景的卫星图像,以研究宇宙的起源。
该笔记本使用了本课程中你熟悉的Python库,但也包含了宇宙学领域的特定参考文献。这是数据科学中的典型情况,即特定领域的技能与数据科学技能相结合。
我们知道你没有天体物理学博士学位,但请不要气馁,因为该笔记本是自解释的,并且是为你们设计的。未来,你很可能会作为一个数据科学家加入一个跨学科团队。因此,这是一个绝佳的机会,可以开始磨练你的技能:理解一个领域问题,并尝试如何将数据科学方法和技术应用于它。
该示例有三个不同的部分,使用了pandas、numpy和matplotlib的高级功能:
- 第一部分:我们将读取由普朗克卫星创建的地图,并探索一种名为HDF5的科学数据格式。
- 第二部分:笔记本将使用NumPy来模拟普朗克卫星在一年观测期间如何扫描天空,并绘制不同参考系下的扫描环。
- 第三部分:笔记本将使用第二部分创建的扫描坐标来模拟对第一部分中使用的地图的观测。
希望你享受阅读这个高级笔记本的过程,并庆祝你在过去九周里取得的巨大进步。
本周的另一个示例笔记本名为“蛋白质数据库分析”。该笔记本由David Dorner开发,他是加州大学圣地亚哥分校数据科学与工程高级研究硕士项目的顶尖学生之一。
虽然这个笔记本的格式与你的最终项目不同,也没有完全包含我们概述的所有步骤,但它包含了优秀最终项目的许多组成部分。具体来说,这个笔记本提供了一个通过API使用结构化科学数据并对其进行可视化的绝佳示例。
PDB是一个大型的半策划、众包蛋白质结构数据存储库。该笔记本使用PDB的RESTful网络服务来访问PDB中可用的数据,并使用Bouquet库来可视化数据。尽管你们大多数人不是生物学家,但我们希望你能探索这个笔记本,寻找可能用于自己项目的想法。

本节课中,我们一起学习了最终项目的完整流程,从选择数据集、提出研究问题,到进行深入分析、批判性思考和最终呈现。我们还预览了两个高级示例,展示了数据科学在跨学科领域(天体物理学和生物信息学)的强大应用。请记住,最终项目是你展示所学技能的机会,选择一个你真正感兴趣的主题,享受探索和创造的过程。

在本节课中,我们将回顾整个课程的学习历程,并对未来的学习方向进行展望。
首先,我们要祝贺你完成了加州大学圣地亚哥分校在EdX平台开设的微硕士项目中的第一门课程。
我们知道,为了走到这一步,你付出了大量的努力。我们希望你能为自己取得的成就感到自豪。
你很可能在开始这门课程时,对Jupyter、NumPy、pandas、数据可视化、机器学习、自然语言处理等本课程的主要主题知之甚少。
通过完成这门课程,你现在已经能够熟练地使用常见的Python库,在Jupyter笔记本中对数据集进行数据分析。
虽然我们向你介绍了机器学习的概念,并偶尔使用了基础统计学,但通过对这些领域更深入的理解,你将能够进行更高级的分析。
因此,你的后续两门课程将是统计学和机器学习。
如果你想处理真正的大型数据集,即那些超过内存容量、有时甚至超过硬盘容量的数据集,你将需要理解分布式计算和并行处理。
这些内容将在第四门课程中学习。但我们不想用接下来将要学习的神奇内容让你感到不知所措。现在是一个值得庆祝的时刻。
你已经完成了这门课程,并在数据科学领域迈出了坚实的一步。
在结束之前,我们要感谢你花费时间与我们一同学习这门课程。
我们也想请你更多地分享你的学习体验。
请给我们提供关于课程的反馈,告诉我们哪些方面做得好,哪些方面可以改进。
同时,也请通过在edX上发布学习故事,让我们了解你如何在未来的职业生涯中运用这门课程的知识。
我们希望你喜欢这门课程,就像我们喜欢开发它并与你一同学习一样。
再次祝贺你。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/248120.html