数据分析实践指南-全-

数据分析实践指南(全)

原文:zh.annas-archive.org/md5/57e2921b835ab1402858e5a73725de10

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎您,感谢您花时间阅读这本书。在这本书中,我将以非常简单易懂的方式带您了解数据分析的演变。本书将介绍现代工具,如Jupyter Notebook和多种Python库,教您如何处理数据。在这个过程中,您将了解许多不同类型的数据以及如何清理、混合、可视化和分析数据以获得有用的见解。

数据素养是阅读、处理、分析和用数据辩论的能力。数据分析是清理和建模您的数据以发现有用信息的过程。本书通过分享经过验证的技术和实际操作示例,结合这两个概念,让您了解如何有效地与数据沟通。

本指南包含实践教程和真实世界示例,易于遵循,将教授您使用 SQL、Python 和 Jupyter Notebook 进行数据分析的概念。

第一章:本书面向对象

本书面向任何希望提高技能以成为数据驱动型个人和专业人士的人。开始阅读本书不需要数据分析或编程的先验知识。任何寻找与数据相关的新职业的人都会喜欢阅读这本书。

本书涵盖内容

第一章,数据分析基础,是对数据分析是什么以及如何使用传统和现代技术的混合来进行分析的简单介绍。

第二章,Python 和安装 Jupyter Notebook 概述,使用开源数据分析工具 Jupyter Notebook 介绍了 Python 编程语言。

第三章,NumPy 入门,您将学习到使用名为 NumPy 的强大 Python 库进行数据分析的关键函数;您还将探索数组和矩阵数据结构。

第四章,创建您的第一个 pandas DataFrame,介绍了 pandas DataFrame 是什么以及如何从不同文件类型源(如 CSV、JSON 和 XML)创建它们。

第五章,在 Python 中收集和加载数据,展示了如何在 Jupyter Notebook 中运行 SQL SELECT查询并将它们加载到 DataFrame 中。

第六章,可视化和处理时间序列数据,通过分解图表的解剖结构来探索制作第一个数据可视化的过程。将解释基本统计、数据来源和元数据(关于数据的数据)。

第七章,探索、清理、精炼和混合数据集,重点关注整理、排序和探索数据集所需的必要概念和数值技能。

第八章,理解连接、关系和聚合,深入探讨了构建高质量数据集以进行进一步分析。将介绍连接和汇总数据的概念。

第九章,绘图、可视化和讲故事,继续教授您如何通过探索额外的图表选项(如直方图和散点图)来可视化数据,以提升您的数据素养和分析技能。

第十章,探索文本数据和非结构化数据,介绍了自然语言处理NLP),这已成为数据分析中必备的技能。本章将探讨您需要了解的概念,以便分析可以提供非结构化数据洞察的叙事自由文本。

第十一章,实用情感分析,涵盖了监督机器学习的基础知识。之后,将进行情感分析的操作演示。

第十二章,整合一切,通过实际案例将书中涵盖的许多概念结合起来,展示了阅读、处理、分析和用数据辩论所需技能。

要充分利用这本书

这本书适合对数据分析领域完全陌生的人。不需要任何先前的数据或编程知识或经验。本书是一步一步的指南,引导您完成安装和练习。

只需具备基本的技术能力。能够下载文件、访问网站和在您的计算机上安装应用程序就足够了。

书中涵盖的软件/硬件 操作系统要求
软件:Jupyter Notebook、Anaconda、Python 3.X、NLTK 任何操作系统(已在 Windows 10 和 macOS X 上测试)
硬件:任何(已在英特尔酷睿 i7、16 GB、235 GB 硬盘上测试)

如果您正在使用这本书的数字版,我们建议您自己输入代码或通过 GitHub 仓库(下一节中提供链接)访问代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。

下载示例代码文件

您可以从www.packt.com的账户下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

您可以通过以下步骤下载代码文件:

  1. www.packt.com登录或注册。

  2. 选择支持选项卡。

  3. 点击代码下载。

  4. 在搜索框中输入书籍名称,并遵循屏幕上的说明。

一旦文件下载完成,请确保您使用最新版本的以下软件解压或提取文件夹:

  • Windows 的 WinRAR/7-Zip

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书代码包也托管在 GitHub 上,地址为github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有来自我们丰富的书籍和视频目录中的其他代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一份包含本书中使用的截图/图表的彩色 PDF 文件。您可以从这里下载:static.packt-cdn.com/downloads/9781838826031_ColorImages.pdf

使用的约定

本书使用了多种文本约定。

CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“因此,purchase_data.iloc[0]purchase_data.ix[0] 都会返回相同的结果。”

代码块设置如下:

product_data = {
 'product a': [13, 20, 0, 10],
 'project b': [10, 30, 17, 20],
 'project c': [6, 9, 10, 0]
} 

任何命令行输入或输出都如下所示:

>cd \
>cd projects
>jupyter notebook

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中如下所示。以下是一个示例:“根据操作系统,例如 Linux,CSV 文件只会包含每行的换行符LF),而不是回车符CR)。”

警告或重要提示如下所示。

小技巧如下所示。

联系我们

我们读者的反馈总是受欢迎的。

一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并通过customercare@packtpub.com给我们发送邮件。

勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,请访问www.packtpub.com/support/errata,选择您的书籍,点击勘误表提交表单链接,并输入详细信息。

盗版:如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过copyright@packt.com与我们联系,并提供材料的链接。

如果您有兴趣成为作者:如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

评论

请留下您的评价。一旦您阅读并使用过这本书,为何不在购买它的网站上留下评价呢?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 可以了解您对我们产品的看法,而我们的作者也可以看到他们对书籍的反馈。谢谢!

如需了解 Packt 的更多信息,请访问 packt.com.

第二章

第一部分:数据分析基础

在本节中,我们将通过使用 Python 和 Jupyter Notebook 从数据中提取有用和可操作的信息来学习如何使用数据语言。我们将从数据分析的基础知识开始,并使用合适的工具帮助您有效地分析数据。在您的 workspace 设置完成后,我们将学习如何使用 Python 中两个流行的开源库来处理数据:NumPy 和 pandas。这将为您理解数据打下基础,以便您为 第二部分:数据发现解决方案 做好准备。

本节包括以下章节:

  • 第一章,数据分析基础

  • 第二章,Python 概述和安装 Jupyter Notebook

  • 第三章,NumPy 入门

  • 第四章,创建您的第一个 pandas DataFrame

  • 第五章,在 Python 中收集和加载数据

数据分析基础

欢迎并感谢您阅读我的书籍。我非常兴奋地分享我对数据的热情,并希望提供资源和见解,以加速您进入数据分析之旅。我的目标是教育、指导和辅导您,在本书中学习成为顶尖数据分析师的技术。在这个过程中,您将获得使用最新开源技术(如 Jupyter Notebook 和 Python)的实践经验。我们将保持在这一技术生态系统中,以避免混淆。然而,您可以确信所学的概念和技能可以跨开源和供应商解决方案迁移,重点关注所有数据相关的事物。

在本章中,我们将涵盖以下内容:

  • 数据分析的发展及其重要性

  • 什么使一个优秀的数据分析师?

  • 理解数据类型及其重要性

  • 数据分类和数据属性解释

  • 理解数据素养

第三章:数据分析的发展及其重要性

首先,我们应该定义什么是数据。您会发现不同的定义,但我会将数据定义为事实、知识和信息的数字化持久存储,用于参考或分析。我的定义的重点应该是单词persistence,因为数字事实在创建它们的计算机关闭后仍然存在,并且可以用于未来的使用。而不是关注正式的定义,让我们讨论数据的世界以及它如何影响我们的日常生活。无论您是在阅读评论以决定购买哪种产品,还是在查看股票价格,消费信息已经变得更容易,以便您做出基于数据的明智决策。

数据已经与各行各业的产品和服务交织在一起,从农业到智能手机。例如,根据其年度报告,新泽西州的美国种植行农场到食物银行慈善机构,每年捐赠超过 150 万磅的新鲜农产品,以喂养该地区需要帮助的人们。美国种植行拥有数千名志愿者,并利用数据在收获季节最大化产量。

随着对成为数据消费者的需求增加,供应方也相应增加,这被定义为数据的生产者。随着技术创新的发展,数据生产的规模也在增加。我将在稍后详细讨论这一点,但大规模的消费和生产可以概括为大数据。美国国家标准与技术研究院的报告将大数据定义为包含大量数据集——主要在数量、速度和/或可变性方面,需要可扩展的架构以实现高效的存储、操作和分析。

大数据的爆炸性增长以3V为特征,即数量速度多样性,已成为数据专业人士广泛接受的概念:

  • 体积是指存储在任何格式(如图像文件、电影和数据库事务)中的数据量,这些数据量以千兆字节、太字节甚至泽字节来衡量。为了提供背景,你可以在一个太字节的存储空间中存储数十万首歌曲或图片。甚至更令人惊讶的是,这些数据存储的成本。例如,根据他们的支持网站,Google Drive 提供高达 5 TB(太字节)的免费存储空间。

  • 速度是指数据生成的速度。这个过程涵盖了数据的生产和消费方式。例如,批量处理是系统之间发送和接收数据块或文件包的方式。现代速度方法都是实时的,数据流处于持续运动的状态。

  • 多样性是指数据可以存储的所有不同格式,包括文本、图像、数据库表和文件。这种多样性由于需要使用不同的技术和技术来处理数据,因此既带来了挑战,也带来了机遇。

理解 3V 对于数据分析非常重要,因为你必须成为数据的好消费者和生产者。简单的疑问,如你的数据是如何存储的、这个文件是什么时候产生的、数据库表在哪里,以及我应该以什么格式存储我的数据分析输出,都可以通过理解 3V 来解决。

对于是否应该将 3V 增加到包括价值可视化真实性,有一些争议——对此我并不认同。不用担心,我们将在本书中涵盖这些概念。

这引导我们得到了数据分析的正式定义,该定义被定义为“一个检查、清洗、转换和建模数据的过程,其目标是发现有用信息、得出结论并支持决策”,正如在《通过数据分析的商业智能综述》中所阐述的。

夏,B. S.,& Gong,P. (2015). 通过数据分析的商业智能综述. Benchmarking,21(2),300-311. doi:10.1108/BIJ-08-2012-0050

我喜欢这个定义的地方在于它关注的是使用数据解决问题,而不是关注使用哪些技术。为了实现这一点,已经有一些重要的技术里程碑、新概念的引入以及打破障碍的人。

为了展示数据分析的发展历程,我整理了几张从 1945 年到 2018 年间的关键事件表,我认为这些事件最具影响力。以下表格包括了如 E.F. Codd 博士这样的创新者,他创造了数据库的概念,以及 iPhone 设备的推出,这催生了移动分析行业。

下面的图表是从多个来源收集的,并集中在一个地方作为表格的列和行,然后使用这个树状图进行可视化。我已在 GitHub 仓库中发布了 CSV 文件以供参考:github.com/PacktPublishing/python-data-analysis-beginners-guide。将信息组织并统一数据在一个地方,使得数据可视化更容易制作,并允许进一步的分析:

收集、格式化和以这种可读格式存储数据的过程展示了成为生产者的第一步。为了使这些信息更容易消费,我在以下表格中按十年总结这些事件:*

十年 里程碑事件数量
1940 年代 2
1950 年代 2
1960 年代 1
1970 年代 2
1980 年代 5
1990 年代 9
2000 年代 14
2010 年代 7

从前面的总结表中,你可以看到大多数这些里程碑事件发生在 1990 年代和 2000 年代。这个分析有洞察力的是,最近的技术创新已经消除了个人与数据工作的进入障碍。在 1990 年代之前,硬件和软件的高购买成本将数据分析领域限制在相对有限的数量职业中。此外,分析所需基础数据的获取成本很高。通常需要高等教育和软件编程或精算学的专业职业。

以一种视觉方式查看这些相同的数据,可以是一个趋势条形图,如图中所示 在这个例子中,条形的高度与前面表格中的信息相同,里程碑事件的数量位于左侧或y轴上。这种数据视觉表示的优点是,它为消费者提供了一个更快的方式,可以查看大多数事件发生的上升模式,而无需扫描前面图表或表格中找到的结果:

理解数据分析的演变过程很重要,因为现在你了解了一些为数据工作开辟机会和职业道路的先驱,以及关键的技术突破,这些突破显著减少了作为消费者生产者做出数据决策的时间。

什么使一个好的数据分析师?

我现在将分解构成优秀数据分析师的因素。根据我的经验,优秀的数据分析师必须渴望学习,并在与数据工作的整个过程中不断提问。这些问题关注的焦点将根据消费结果的目标受众而变化。要成为数据分析领域的专家,需要出色的沟通技巧,以便你能够理解如何将原始数据转化为可以以积极方式影响变革的见解。为了更容易记住,使用以下缩写词来帮助你提高数据分析师技能。

了解你的数据 (KYD)

了解你的数据就是理解用于创建数据的源技术,以及用于存储它的业务需求和规则。提前进行研究,了解业务是什么以及数据是如何被使用的。例如,如果你在与销售团队合作,了解是什么推动了他们团队的成功。他们有每日、每月还是每季度的销售配额?他们是否进行月末/季末报告,这些报告需要发送给高级管理层,并且必须准确,因为它对公司有财务影响?通过询问数据将如何被消费的问题来了解更多关于源数据的信息,将有助于你在需要交付结果时集中分析。

KYD 还涉及数据血缘,即理解数据最初是如何获取的,包括使用的技术以及在此之前、期间和之后发生的转换。参考 3Vs,以便你能够有效地传达关于数据的常见问题的答案,例如这些数据是从哪里来的,或者谁负责维护数据源。

客户之声 (VOC)

VOC 的概念并不新鲜,多年来在大学里作为在销售、营销和许多其他业务运营中应用的知名概念而被教授。VOC 是通过在学习或倾听他们在使用公司产品或服务之前、期间和之后的需求来理解客户需求的概念。这个概念的相关性至今仍然重要,应该应用于你参与的每一个数据项目。这个过程是在你查看数据之前应该对数据分析结果的使用者进行访谈的地方。如果你与业务用户合作,通过写下他们试图回答的具体业务问题来倾听他们的需求。

与他们安排一个工作会议,在那里你可以进行对话。确保你关注他们的当前痛点,例如整理用于决策的所有数据所需的时间。每个月完成这个过程需要三天吗?如果你能提供一个自动化数据产品或仪表板,将时间缩短到几个鼠标点击,你的数据分析技能将使你的业务用户看起来像英雄。

在一次当地大学的科技讲座中,我被问到了 KYD 和 VOC 之间的区别。我解释说,两者都很重要,并且都专注于沟通和学习更多关于该领域或业务的知识。关键的区别在于准备与呈现。KYD 完全是关于在谈话专家之前提前做作业以做好准备。VOC 完全是关于倾听业务或消费者对数据的需要。

始终保持敏捷(ABA

敏捷方法已成为行业在应用、网页和移动开发软件开发生命周期(SDLC)中的常见做法。使敏捷项目管理过程成功的一个原因是它通过使用数据和可用功能,在业务和技术团队之间建立了一个交互式的沟通渠道,以迭代地交付业务价值。

敏捷过程包括创建具有共同主题的故事,开发团队在 2-3 周的冲刺中完成任务。在这个过程中,理解每个故事中的什么为什么很重要,包括业务价值/你试图解决的问题。

敏捷方法中有仪式,开发者和业务赞助者聚集在一起来捕捉需求,然后交付增量价值。这种价值提升可能从可访问的新数据集到应用中添加的新功能等任何事物。

请参阅以下图表,以获得这些概念的直观表示。注意,这些概念不是线性的,并且应该需要多次迭代,这有助于在数据分析前、中、后所有参与人员之间改善沟通:

图片

最后,我认为一个好的数据分析师最重要的特质是对与数据工作的热情。如果你的热情可以通过不断学习所有关于数据的知识来激发,那么它将变成一段终身且充实的人生旅程。

理解数据类型及其重要性

正如我们通过3Vs所发现的,数据以各种形状和大小存在,因此让我们分解一些关键数据类型,更好地理解它们为什么重要。首先,让我们从总体上把数据分为非结构化半结构化结构化

非结构化数据

非结构化数据的概念,其本质是文本,自 1990 年代以来一直存在,包括以下示例:电子邮件消息正文、推文、书籍、健康记录和图像。非结构化数据的一个简单例子是作为 自由文本 分类电子邮件消息正文。自由文本 可能有一些人类可以识别的明显结构,例如用于分隔段落的空白空间、日期和电话号码,但让计算机识别这些元素则需要编程来将任何数据元素分类为这种类型。使自由文本对数据分析具有挑战性的是其不一致性,尤其是在尝试处理多个示例时。

当处理非结构化数据时,由于自由文本的性质,包括拼写错误、日期的不同分类等,将存在不一致性。始终对用于整理数据的流程或代码进行同行评审。

半结构化数据

接下来,我们有半结构化数据,它与非结构化数据类似,但关键区别是增加了 标签,这些标签是用于创建自然层次的关键词或任何分类。半结构化数据的例子包括 XML 和 JSON 文件,如下面的代码所示:

{
  "First_Name": "John",
  "Last_Name": "Doe",
  "Age": 42,
  "Home_Address": {
    "Address_1": "123 Main Street",
    "Address_2": [],
    "City": "New York",
    "State": "NY",
    "Zip_Code": "10021"
  },
  "Phone_Number": [
    {
      "Type": "cell",
      "Number": "212-555-1212"
    },
    {
      "Type": "home",
      "Number": "212 555-4567"
    }
  ],
  "Children": [],
  "Spouse": "yes"
}

这种以 JSON 格式编写的代码允许存在自由文本元素,例如街道地址、电话号码和年龄,但现在已创建了用于识别这些字段和值的 标签,这是一个称为 键值对 的概念。这种键值对概念允许对具有分析结构的数据进行分类,例如过滤,但仍然具有足够的灵活性,可以根据需要更改元素以支持非结构化/自由文本。半结构化数据最大的优势是改变数据存储的底层架构的灵活性。架构是传统数据库系统的基础概念,它定义了数据必须如何持久化(即存储在磁盘上)。

半结构化数据的缺点是,您可能仍然会发现数据值的不一致性,这取决于数据是如何被捕获的。理想情况下,一致性的负担转移到 用户界面UI),这将具有编码标准和业务规则,如必填字段,以提高质量,但作为一个实践 KYD 的数据分析师,您应该在项目中进行验证。

结构化数据

最后,我们有 结构化 数据,这是数据库中最常见的类型,也是从应用程序(应用程序或软件)和代码中创建的数据。结构化数据最大的好处是每个记录之间的一致性和相对较高的质量,尤其是在同一数据库表中存储时。数据和结构的一致性是分析的基础,这使得结构化数据的产生者和消费者可以得出相同的结果。数据库或 数据库管理系统DBMS)和 关系数据库管理系统RDMS)的主题非常广泛,这里不会涉及,但了解一些相关知识将有助于你成为一名更好的数据分析师。

以下图是数据库中可能找到的三个表的基本 实体-关系ER)图:

在这个例子中,每个 实体 都将代表数据库中存储的物理表,命名为 carpartcar_part_bridge。汽车和零件之间的关系由名为 car_part_bridge 的表定义,可以被称为多个名称,例如 bridgejunctionmappinglink 表。表中每个字段的名称将在左侧,例如 part_idnamedescription,这些在 part 表中可以找到。

car_idpart_id 字段名称旁边的 pk 标签有助于识别每个表的唯一键。这允许一个字段唯一地标识表中找到的每个记录。如果一个表中的 主键 在另一个表中存在,它将被称为 外键,这是定义表之间关系并最终将它们连接在一起的基础。

最后,在字段名称旁边右侧对齐的文本是每个字段的类型。我们将在下一节中介绍这个概念,你现在应该对识别和分类数据的概念感到舒适。

常见的数据类型

数据类型是编程语言中一个众所周知的概念,并且在许多不同的技术中都可以找到。我将定义简化为,存储数据的详细信息及其预期用途。数据类型还会在数据存储在磁盘或内存时为每个数据值创建一致性。

数据类型将根据用于创建结构的软件和/或数据库而有所不同。因此,我们不会涵盖所有不同编码语言中的所有不同类型,但让我们通过几个例子来了解一下:

常见数据类型 常见简称 示例值 示例用法
整数 int 1235 计数发生次数、求和值或值的平均值,例如总和(点击次数)
布尔值 bit TRUE 条件测试,例如 if 销售额 > 1,000, true 否则 false
地理空间 floatspatial 40.229290, -74.936707 基于经纬度的地理分析
字符串/文本 char A 标签、分类或数据分组
浮点数 floatdouble 2.1234 销售额、成本分析或股价分析
字母数字字符串 blobvarchar United States 标签、分类、编码或数据分组
时间 timetimestampdate 8/19/2000 时间序列分析或年度比较

技术在不断变化,遗留系统将提供机会看到可能不常见的数据类型。处理新数据类型时,最好的建议是验证由SME主题专家)或系统管理员创建的源系统,或者要求提供包含用于持久化数据的活动版本的文档。

在前面的表格中,我创建了一些常见数据类型的总结。理解数据类型之间的差异,以便确定可以对每个数据值执行哪种类型的分析,这是非常重要的。数值数据类型,如整数(int)、浮点数(float)或double,用于计算值,如销售额总和、苹果数量或股票的平均价格。理想情况下,记录的源系统应强制执行数据类型,但可能会有例外。

随着你数据分析技能的提升,帮助解决数据类型问题或提出改进建议,将使整个组织的报告质量和准确性得到提高。

在前面表格中定义为字符(char)和字母数字字符串(varcharblob)的字符串数据类型可以表示为文本,如单词或完整的句子。时间是一个特殊的数据类型,可以用多种方式表示和存储,例如12 PM EST或日期08/19/2000。考虑地理坐标,如纬度和经度,这些坐标可以根据源系统以多种数据类型存储。

本章的目标是向您介绍数据类型的概念,未来的章节将直接提供实际操作经验。数据类型之所以重要,是因为它们可以避免在展示分析事实和见解时出现不完整或不准确的信息。无效或不一致的数据类型也会限制创建准确图表或数据可视化的能力。最后,良好的数据分析是建立对结论完整性的信心和信任,这些结论由支持您分析的定义良好的数据类型所支持。

数据分类和数据属性解释

现在我们对数据类型及其重要性有了更多的了解,让我们来分析不同的数据分类和不同的数据属性类型。首先,让我们通过以下总结图来总结所有可能的组合:

图片

在前面的图中,直接位于数据下面的方框有三种分类数据的方法,即连续分类离散

连续数据是可以测量的,使用数值数据类型进行量化,并且具有连续的范围和无限的可能性。此图中的底部方框是示例,以便您可以轻松找到它们进行参考。连续数据的示例包括股票价格、磅重和时间。

分类(描述性)数据将具有作为“字符串”数据类型的值。分类数据是“有资格的”,因此它将描述某个特定的事物,如人、地点或事物。一些例子包括原产国、一年中的月份、不同类型的树木,以及您的家庭称谓。

虽然数据被定义为分类数据,但不要假设所有值都相同或一致。月份可以存储为“1,2,3;Jan,Feb,Mar;或 January,February,March”,或者任何组合。您将在第七章“探索数据清洗、精炼和融合数据集”中了解更多关于如何清理和规范您的数据以进行一致分析的内容。第七章链接。

一个“离散”数据类型可以是连续的或分类的,这取决于它是如何用于分析的。例如,包括公司中的员工数量。您必须有一个整数/整个数字来表示每个员工的计数,因为您永远不能有部分结果,例如半个员工。由于其数值属性,离散数据在本质上具有连续性,但也具有使其类似于分类的限制。另一个例子是轮盘赌盘上的数字。轮盘上有从1360,或00的整数限制,玩家可以下注,而且根据值,这些数字可以分类为红色、黑色或绿色。

如果只有两个离散值存在,例如“是/否”或“真/假”或“1/0”,它也可以被归类为二进制。

数据属性

现在我们已经了解了如何分类数据,让我们分解可用的属性类型,以便更好地了解您如何使用它们进行分析。分解类型的最简单方法是先从您计划如何使用数据值进行分析开始:

  • 名义数据是指可以区分不同值但不必一定对它们进行排序的数据。它本质上是定性的,因此可以将名义数据视为标签或名称,如“股票”或“债券”,因为它们是字符串值,不能在上面进行数学运算。使用名义值,没有额外信息,您无法确定“股票”或“债券”哪个更好或更差。

  • 有序数据是有序数据,其中存在排名,但值之间的距离或范围无法定义。有序数据使用标签或名称进行定性,但现在值将具有自然或定义的序列。与名义数据类似,有序数据可以计数但不能用所有统计方法进行计算。

一个例子是将1赋值为低,2赋值为中,3赋值为高。这有一个自然的顺序,但低和高之间的差异不能单独量化。分配给值的数据可能是任意的,或者背后有额外的业务规则。

另一个常见的有序数据例子是自然层次结构,如州、县和市,或者祖父、父亲和儿子。这些值之间的关系定义良好,通常无需额外信息即可理解。因此,儿子会有父亲,但父亲不能是儿子。

  • 间隔数据类似于有序数据,但数据点之间的距离是均匀的。磅秤上的重量是一个很好的例子,因为从51010152025的值之间的差异都是相同的。注意,并非所有算术运算都可以在间隔数据上执行,因此理解数据的上下文以及如何使用它变得很重要。

温度是一个很好的例子来展示这个范式。你可以记录每小时的数据值,甚至提供每日的平均值,但按日或周汇总数据并不会为分析提供准确的信息。参见以下图表,它提供了一个特定日期的小时温度。注意x轴将小时分开,而y轴提供平均值,标记为平均温度,单位为华氏度。每个小时之间的值必须是平均值或均值,因为温度的累积会提供误导性的结果和错误的分析:

图片

  • 比率数据允许进行所有算术运算,包括求和、平均、中位数、众数、乘法和除法。前面讨论的integerfloat数据类型被归类为比率数据属性,这些属性反过来也是数值/定量的。此外,time也可以被归类为比率数据,然而,我决定进一步细分这个属性,因为它在数据分析中经常被使用。

注意,关于比率数据属性的高级统计细节,本书没有涉及,例如具有绝对或真实零点,所以我鼓励你更多地了解这个主题。

  • 时间数据属性是一个丰富的主题,你在数据分析之旅中会经常遇到。时间数据包括日期和时间或任何组合,例如,时间为HH:MM AM/PM,如12:03 AM;年份为YYYY,如1980;时间戳表示为YYYY-MM-DD hh:mm:ss,如2000-08-19 14:32:22;或者甚至日期为MM/DD/YY,如08/19/00。处理时间数据时,重要的是要识别每个值之间的间隔,这样你才能准确测量它们之间的差异。

在许多数据分析项目中,你可能会发现时间数据值序列中的空白。例如,你得到一个数据集,其范围在08/01/201908/31/2019之间,但只有 25 个不同的日期值,而不是 30 天的数据。这种发生的原因有很多,包括系统故障导致日志数据丢失。如何处理这些数据空白将取决于你必须执行的分析类型,包括填补缺失结果的需要。我们将在第七章,探索清理、精炼和合并数据集中介绍一些示例。

理解数据素养

数据素养被拉胡尔·巴加瓦和凯瑟琳·迪加尼亚定义为阅读、处理、分析和用数据辩论的能力。在本章中,我指出了数据以各种形状和大小出现的情况,因此创建一个在不同受众之间关于数据沟通的共同框架成为一项重要的技能要掌握。

数据素养成为两个或更多不同技能或经验的人之间回答数据问题的共同分母。例如,如果一个销售经理想要验证季度报告中图表背后的数据,让他们精通数据语言将节省时间。通过直接询问与工程团队相关的数据类型数据属性,而不是毫无目的地寻找这些细节,可以节省时间。

让我们分解数据素养的概念,以帮助识别它如何应用于你的个人和职业生活。

阅读数据

阅读数据意味着什么?阅读数据是消费信息,这些信息可以是任何格式,包括图表、表格、代码或电子邮件正文。

阅读数据不一定能提供消费者所有问题的答案。拥有领域专业知识可能需要理解数据集是如何、何时以及为什么被创建的,以便消费者能够完全解释底层数据集。

例如,你是一名数据分析师,你的同事通过电子邮件发送了一个带有“仅供参考”主题行且正文没有其他信息的文件附件。我们现在从什么是一个好的数据分析师?这一部分知道,我们应该开始询问关于文件附件的问题:

  • 创建文件使用了哪些方法(人工还是机器)?

  • 使用了哪些系统和工作流程来创建文件?

  • 谁创建了文件以及它是何时创建的?

  • 这个文件多久刷新一次,是手动还是自动?

提出这些问题有助于你理解数据血缘的概念,这可以识别数据集是如何创建的过程。这将确保阅读数据将导致理解所有方面,从而自信地做出决策。

与数据工作

我将与数据工作定义为创建数据集的人或系统,使用任何技术。用于创建数据的技术种类繁多,可能简单到有人只是在电子表格中输入行和列,也可能是一个软件开发者使用 Python 代码中的循环和函数来创建一个以管道分隔的文件。

由于编写数据因专业知识和工作职能而异,从数据素养的角度来看,一个关键的经验教训是数据的生产者应该意识到数据将被如何使用。理想情况下,生产者应该记录数据创建的细节,包括创建的时间、地点以及刷新的频率。发布这些信息将使元数据(关于数据的资料)民主化,以改善任何阅读和操作数据的人之间的沟通。

例如,如果你的数据集中有一个时间戳字段,它是使用UTC协调世界时)还是EST东部标准时间)?通过包括假设和原因,说明数据为什么以特定格式存储,与数据一起工作的人或团队能够通过改善分析沟通成为良好的数据公民。

数据分析

分析数据始于建模和结构化以回答业务问题。数据建模是一个广泛的话题,但为了数据素养的目的,它可以简化为维度度量。维度是独特的名词,如人、地点或事物,而度量是基于动作的动词,然后进行聚合(总和、计数、最小值、最大值和平均值)。

构建任何数据可视化和图表的基础在于数据建模,而大多数现代技术解决方案都内置了这一功能,所以你可能已经在不知不觉中建模了数据。

一个快速解决方案来帮助确定数据应该如何用于分析,可能就是一个数据字典,它被定义为一个关于数据的集中式信息库,如意义、与其他数据的关系、来源、用途和格式

你可能在源系统的帮助页面或 GitHub 仓库中找到一个数据字典。如果你没有从文件创建者那里收到一个,你可以自己创建一个,并使用它来询问关于数据的问题,包括假设的数据类型、数据质量和识别数据缺口。

创建数据字典还有助于验证假设,并在与他人沟通数据时提出关于数据的问题。创建数据字典的最简单方法是将源数据的前几行转置,这样行就变成了列。如果你的数据有标题行,那么第一行就变成了所有可用字段的列表。让我们通过一个例子来了解一下如何从数据中创建自己的数据字典。这里,我们有一个表示产品客户按季度销售的销售源表:

产品 客户 第一季度 第二季度 第三季度 第四季度
产品 1 客户 A $          1,000.00 | $          2,000.00 $          6,000.00
产品 1 客户 B $          1,000.00 | $              500.00
产品 2 客户 A $          1,000.00
产品 2 客户 C $          2,000.00 | $          2,500.00 $          5,000.00
产品 3 客户 A $          1,000.00 | $          2,000.00
产品 4 客户 B $          1,000.00 | $          3,000.00
产品 5 客户 A $          1,000.00

在以下表中,我已经将先前的源表转置,以创建一个新的用于分析的表,这创建了一个初始的数据字典。左侧的第一列变成了源表中所有可用字段的列表。正如您从字段中可以看到的,表头行的记录 1记录 3现在变成了数据样本行,但保留了源表中每行的完整性。以下表右侧的最后两列,标记为估计数据类型维度或度量,被添加以帮助定义此数据在分析中的用途。理解数据类型并将每个字段分类为维度或度量将有助于确定我们可以执行哪种类型的分析以及每个字段如何在数据可视化中使用:

字段名称 记录 1 记录 2 记录 3 估计数据类型 维度或度量
产品 产品 1 产品 1 产品 2 varchar 维度
客户 客户 A 客户 B 客户 A varchar 维度
第一季度 $   1,000.00 float 度量
第二季度 $   2,000.00 | $      1,000.00 $       1,000.00 float 度量
第三季度 $   6,000.00 | $          500.00 float 度量
第四季度 float 度量

使用这种技术可以帮助您就数据提出以下问题,以确保您理解结果:

  • 这个数据集代表哪一年,或者它是多年度数据的累积?

  • 每个季度代表的是日历年还是财政年?

  • 产品 5 是否是在第四季度首次推出,因为该产品在第一季度到第三季度期间没有任何客户销售记录?

关于数据的争论

最后,让我们谈谈为什么以及我们应该如何就数据展开争论。挑战和捍卫图表或数据表中的数字有助于建立可信度,实际上在许多情况下是在幕后进行的。例如,大多数数据工程团队会在数据摄入期间进行各种检查和平衡,例如警报,以避免信息丢失。额外的检查还包括检查日志文件中的异常或数据处理的错误规则。

从消费者的角度来看,信任并验证是一个好的方法。例如,当查看一篇可信新闻文章中发布的图表时,你可以假设故事背后的数据是准确的,但你应该也验证源数据的准确性。首先应该问的问题是:底层的图表是否包含了指向公开可用的数据集的来源?fivethirtyeight.com网站在提供访问原始数据和创建新闻故事中分析图表所使用的方法细节方面做得非常好。将底层数据集和收集数据的过程公之于众,可以开启关于数据背后的如何、什么和为什么的对话,并且是反驳错误信息的好方法。

作为数据分析师和数据输出创建者,能够捍卫你的工作应该受到欢迎。拥有诸如数据字典和 GitHub 仓库之类的文档,以及记录生产数据所使用的方法,将建立与观众的信任,并减少他们做出数据驱动决策的时间。

希望你现在看到了数据素养的重要性以及它如何被用来改善消费者和生产者之间数据交流的各个方面。任何语言,练习都会带来进步,所以我邀请你探索一些有用的免费数据集来提高你的数据素养。

这里有一些开始的方法:

让我们从Kaggle网站开始,该网站是为了帮助公司举办数据科学竞赛而创建的,以使用数据解决复杂问题。通过探索这些数据集并回顾本章学到的概念,如识别每个字段的数据类型和确认存在数据字典,来提高你的阅读和与数据打交道的技能。

接下来是来自五三八的支持数据,这是一个提供从体育到政治等分析内容的数据新闻网站。我喜欢他们的过程是因为他们提供了透明度,通过公开 GitHub 链接到他们的源数据和关于数据背后方法论的讨论。

另一个重要的数据开源是世界银行,它提供了丰富的选项,用于在全球范围内消费或生产数据,以帮助通过数据改善生活。大多数数据集都受创意共享许可的约束,它规定了数据的使用方式和时间,但使它们免费可用则打开了将公共和私人数据结合起来的机会,并节省了大量的时间。

摘要

在我们继续前进之前,让我们回顾一下本章所学的内容以及获得的技术技能。首先,我们简要回顾了数据分析的历史以及数据的科技演变,通过向使使用现代工具和技术处理数据成为可能的人和里程碑事件致敬。我们通过一个数据可视化趋势图表的例子展示了最近的技术创新如何改变数据行业。

我们从消费者和生产者的角度讨论了为什么数据变得重要,通过讨论使用结构化、半结构化和非结构化示例来识别和分类数据的概念,以及大数据的3V体积速度多样性

我们使用 KYD、VOC 和 ABA 技术回答了“什么使一个好的数据分析师”的问题。

然后,我们通过探讨数字(整数和浮点数)与字符串(文本、时间、日期和坐标)之间的区别,更深入地理解了数据类型。这包括分解数据分类(连续、分类和离散)以及理解数据属性类型。

我们通过介绍数据素养的概念及其通过改善他们之间的沟通对数据消费者和生产者的重要性来结束本章。

在下一章中,我们将通过安装和设置数据分析环境来获得更多实践经验,并开始应用关于数据的所学概念。

进一步阅读

以下是一些您可以参考的链接,以获取更多关于以下主题的信息:

Python 和安装 Jupyter Notebook 概述

既然你对数据素养背后的概念和数据分析的演变有了更好的理解,让我们设置自己的环境,以便你可以处理数据。在本章中,我们将介绍 Python 编程语言,以及一个流行的工具 Jupyter Notebook,它用于运行数据分析的命令。我们将逐步讲解安装过程,并讨论关键概念,以了解为什么它们对于数据分析是必需的。到本章结束时,你将拥有一个工作站,可以运行一个hello world程序,这将有助于增强你进一步学习更深入概念的信心。

在本章中,我们将涵盖以下内容:

  • 安装 Python 和使用 Jupyter Notebook

  • 存储和检索数据文件

  • Hello World!——运行你的第一个 Python 代码

  • 探索 Python 包

第四章:技术要求

这是本书的 GitHub 仓库链接:github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter02.

此外,你可以从以下链接下载并安装所需的软件:www.anaconda.com/products/individual

安装 Python 和使用 Jupyter Notebook

我首先要承认,这个章节在未来可能会变得过时,因为在你工作站上安装开源软件可能是一个痛苦的过程,在某些情况下,它正被预装虚拟机或云版本所取代。例如,微软提供免费 Azure 订阅选项,用于云托管的 Jupyter Notebook。

理解软件版本、硬件、操作系统OS)差异和库依赖的所有依赖项可能很复杂。此外,你的 IT 部门在企业环境中对软件安装的规定可能有安全限制,禁止访问你的工作站文件系统。很可能会随着云计算的更多创新,大多数步骤已经在事先完成,从而消除了安装软件的需要。

话虽如此,我将带你通过安装 Python 和 Jupyter Notebook 的过程,沿途指出提示和陷阱,以教育你关键概念。我会将使用这些技术工具处理数据比作开车。驾驶的能力不应该依赖于你修理汽车引擎的能力!仅仅知道你需要一个引擎就足够你驾驶并前进。所以,我的重点是快速设置你的工作站以进行数据分析,而不关注这些强大技术背后的细节层次。

创建 Jupyter 笔记本应用程序的开源项目始于 2014 年的 iPython。iPython 中存在的许多功能今天仍然存在于 Jupyter 中,例如,用于运行 Python 命令的交互式 GUI 和并行处理。有一个内核来控制你的计算机 CPU、内存和文件系统之间的输入/输出。最后,笔记本还有一个功能,可以将所有命令、代码、图表和注释收集到一个单一的、带有.ipynb扩展名的可共享文件中。

为了提供一些关于 Jupyter 笔记本在数据分析中如何变得流行的背景信息,我发现了一个由 Peter Parente 创建的公共 GitHub 仓库,该仓库收集了自 2014 年以来在 GitHub 上找到的每日.pynb文件数量。增长是指数级的,因为到 2019 年 11 月,数量从 65,000 多增长到 570 万,这意味着在过去五年中每年都在翻倍!

使用 Jupyter 笔记本的第一个先决条件是安装 Python。我们将使用 3.3 或更高版本的 Python,并且有两种方法可以用来安装软件:直接下载或包管理器。直接下载将使你在工作站上的安装有更多的控制权,但这也需要额外的时间来管理依赖库。话虽如此,使用包管理器安装 Python 已经成为首选方法,因此,我在本章中介绍了这种方法。

Python 是一种功能强大的编程语言,支持多个操作系统平台,包括 Windows、macOS 和 Linux。我鼓励你阅读更多关于这种强大软件语言的历史以及其创造者 Guido van Rossum 的信息。

Python 在本质上是一种命令行编程语言,因此你必须习惯于从提示符运行一些命令。当我们完成安装后,你将拥有一个 Python 命令行窗口,如果你的工作站运行 Windows 操作系统,它将看起来如下截图所示:

图片

将 Python 安装视为达到目的的手段,因为我们真正想要用作数据分析师的是 Jupyter 笔记本,它也被称为用于运行代码和调用库的集成开发环境IDE),它是一个自包含的图形用户界面GUI)。

由于我推荐使用包管理器进行安装,你必须做出的第一个决定是选择哪个包管理器来在你的计算机上安装。包管理器旨在简化开源库、你的操作系统和软件之间的版本和依赖层。最常见的是condapipdocker

通过研究差异,我更喜欢conda而不是pip,尤其是对于刚开始的人来说,如果您不熟悉运行命令行命令和在 PC 上直接管理软件安装,我更推荐 Anaconda。因为它包括了 Python、几个用于数据分析的流行库以及 Jupyter,所有这些都在下载包中。

记住,目标是让 Jupyter Notebook 在您的工作站上运行起来,所以请随意选择安装替代方案,尤其是如果您更喜欢命令行界面CLI)的话。

安装 Anaconda

按照以下步骤安装 Anaconda。对于这个教程,我选择了 Windows 操作系统安装程序,但无论选择哪个,安装截图都将类似:

  1. 根据您的工作站操作系统选择所需的安装程序来下载软件。为此,请导航到 Anaconda Distribution 页面,该页面应类似于以下截图,并可在www.anaconda.com/找到:

图片

  1. 下载软件并在您的 PC 上启动安装程序后,您应该会看到如下所示的设置向导截图:

图片

  1. 在安装向导中选择默认选项,您应该会看到如下所示的类似消息:

图片

  1. 现在 Anaconda 安装完成后,您必须从您的 PC 上启动 Anaconda Navigator 应用程序,如下所示(使用 Windows 操作系统)。由于有多个操作系统选项可用,如 Windows、macOS 或 Ubuntu,您的屏幕将与此截图有所不同:

图片

我认为安装过程类似于为什么艺术家需要购买画布、画架和材料来开始绘画。现在我们已经安装并可以使用名为 Anaconda 的工作环境了,您就可以启动 Jupyter 并创建您的第一个笔记本。

运行 Jupyter 并为数据分析安装 Python 包

软件安装到您的 PC 后,启动 Jupyter 笔记本可以通过两种方式之一完成。第一种是通过 Anaconda Prompt 中的命令行提示符使用jupyter notebook命令,其外观类似于以下截图:

图片

您还可以使用 Anaconda Navigator 软件,在 Jupyter Notebook 的“我的应用”中点击启动按钮,如下所示:

图片

两种选项都将启动一个新的网络浏览器会话,使用 http://localhost:8888/tree URL,这被称为 Jupyter 仪表板。如果您没有看到以下截图**所示的内容,您可能需要重新安装 Anaconda 软件,或者检查防火墙端口是否阻止了命令或依赖项。在企业环境中,您可能需要审查您的公司政策或请求 IT 支持:

如果您想尝试 JupyterLab 而不是 Jupyter Notebook,任何一种解决方案都可以工作。JupyterLab 使用与经典 Jupyter Notebook 完全相同的 Notebook 服务器和文件格式,因此它与现有的笔记本和内核完全兼容。经典笔记本和 JupyterLab 可以在同一台计算机上并排运行。您可以轻松地在两个界面之间切换。

注意,Jupyter 默认根据其安装方式访问您工作站上的文件系统。在大多数情况下,这应该足够了,但如果您想更改默认的项目 home/root 文件夹,您可以使用 Anaconda Prompt 轻易地更改它。只需在输入 jupyter notebook 命令之前运行 cd 命令来更改目录。

例如,我在 Windows PC 的本地 c:\ 驱动器路径上首先创建了一个 project 文件夹,然后使用以下命令运行 Anaconda Prompt 窗口:

>cd \
>cd projects
>jupyter notebook

如果您按照这个示例操作,如果您使用的是 Windows 操作系统,您的命令提示符窗口应该看起来像以下截图:

完成后,Jupyter 会话中显示的文件和文件夹列表将是空的,您的会话将类似于以下截图:

现在,Jupyter 软件应该已经在您的工作站上积极运行,准备好浏览所有可用的功能,我们将在下一部分介绍。

存储和检索数据文件

我喜欢使用 Jupyter 的原因是它是一个包含数据分析的自包含解决方案。我的意思是,您可以在一个地方与文件系统交互,添加、更新和删除文件夹,以及运行 Python 命令。随着您继续使用这个工具,我认为您会发现与在多个窗口、应用程序或工作站上的系统之间跳转相比,保持在一个生态系统中导航要容易得多。

让我们从熟悉添加、编辑或删除文件的菜单选项开始。Jupyter 默认通过列出从安装目录路径可访问的所有文件和文件夹来列出仪表板。这可以配置为更改起始文件夹,但我们将使用 Windows 默认设置。在以下截图中,我已经用字母突出显示了 Jupyter 仪表板的重要部分,以便于参考:

A节中,当在个人工作站上运行时,URL 默认为http://localhost:888/tree。如果笔记本托管在服务器或云上,此 URL 将更改。注意,当您在B节中选择文件夹或文件时,URL 地址将更改为跟随您选择的位置和路径。

B节中,您将找到仪表板可见的文件夹或文件的层次结构。如果您点击任何文件,它将尝试在编辑器中打开它,无论该文件是否可由 Jupyter 使用。编辑器可读取的文件扩展名包括.jpeg.png.svg格式的图像;半结构化数据文件,如.json.csv.xml;以及代码,如.html.py(Python)和.js(JavaScript)。请注意,当打开文件时,URL 路径将从tree参数词更改为edit

如果编辑器无法识别文件,它将在第一行提供错误信息,并告诉您原因,类似于以下截图:

图片

C节中,您可以选择和过滤仪表板上显示的一个或多个文件或文件夹。这可以在创建多个笔记本和组织用于分析的数据文件时用于组织项目工作空间。一旦选择了任何文件或文件夹,标题“选择项目以执行操作”将更改为操作按钮重命名复制和一个红色垃圾桶图标,它将删除文件或文件夹,如以下截图所示:

图片

在仪表板上,您还会注意到标记为“文件”、“运行”和“集群”的标签页。这些标签页由 Jupyter 应用程序使用,以帮助您保持方向并跟踪正在积极运行的过程。集群是一个高级功能,超出了本书的范围。我们已经在B节中介绍了“文件”标签页。

让我们讨论一下“运行”标签页。它有两个部分:终端,它将包含 Windows 操作系统中的 Powershell 等系统 shell 命令;以及笔记本,它将显示所有正在使用的活动笔记本。一旦我们创建了一些笔记本,我鼓励您刷新浏览器以查看哪些笔记本文件是活动的,以便更好地理解这一功能。如果需要终止一个无响应或占用过多计算机资源(CPU/RAM)的活动笔记本,请使用“关闭”按钮。

D节中,您将看到一个“上传”按钮,允许您将文件添加到仪表板中的任何已导航文件夹。新按钮包含一个子菜单,可以创建文本文件、文件夹或 Python 3 笔记本。

Hello World!– 运行您的第一个 Python 代码

现在我们对仪表板及其导航有了更好的理解,让我们创建我们的第一个笔记本并运行一些 Python 代码。最简单的方法是点击新建按钮,然后在子菜单中选择 Python 3。这将打开浏览器中的一个新标签页或窗口,其外观类似于以下截图:

图片

我建议将任何笔记本的无标题文件重命名,以便以后更容易找到它们。为此,从文件菜单中选择重命名,如以下截图所示,并将其重命名为hello_world或相关的项目名称。一旦点击 OK 按钮,页面顶部的标题栏将显示新名称:

图片

通过重命名笔记本,将创建一个以.ipynb扩展名的新文件,其中包含所有内容的 JSON 格式。这有助于使笔记本文件可共享,并有助于版本控制,它是文件中更改的审计跟踪。

您可以通过选择编辑菜单中的编辑笔记本元数据来查看实际的 JSON 元数据内容。结果将类似于以下截图:

图片

笔记本的 UI 看起来与其他今天使用的现代网络软件非常相似,因为它是为了便于导航而设计的。以下菜单选项是易于使用的图标,统称为笔记本工具栏,它支持键盘快捷键,以优化您使用工具时的工作流程。您可以在帮助菜单中找到用户界面之旅和键盘快捷键,如以下截图所示。我建议您浏览它们,以查看所有可用的功能:

图片

一旦您对帮助菜单选项感到舒适,让我们通过在笔记本单元格中输入print("hello world")命令来编写您的第一个代码,该单元格默认为In []:。记住,如果您使用鼠标在笔记本中导航,您必须点击单元格以选择它并出现光标。

在命令后按Enter键只会创建一个用于更多输入的第二行。您必须使用键盘快捷键、单元格菜单或工具栏图标来执行任何命令。

一旦你在单元格中输入了print("hello world")命令并点击了以下选项之一。运行命令的选项如下:

  • 点击工具栏中的![图片]按钮。

  • 从单元格菜单中选择运行单元格。

  • 按下Shift + EnterCtrl + Enter 键。

屏幕应类似于以下截图:

图片

恭喜你,你已经创建了你的第一个 Jupyter 笔记本并运行了你的第一个命令!点击文件菜单中的关闭和停止选项返回到仪表板。

创建项目文件夹层次结构

现在我们已经覆盖了基础知识,让我们遍历一个目录以找到特定的文件,并创建一个项目文件夹层次结构,为未来的数据分析学习模块做准备。我建议在你的工作站上创建一个起始的projects文件夹,以保持所有笔记本和数据井然有序。标准的企业目录结构因公司而异,但设置一个带有子文件夹的基本结构使过程可移植,并有助于与他人共享工作。以下截图显示了项目文件夹模板示例:

图片

在整本书中,我将使用章节编号作为projectname来使每个目录子文件夹,如datanotebooks,模块化、独立且易于跟踪。你的工作站目录结构和树应该与本书的 GitHub 仓库相匹配,以便更容易同步你的文件和文件夹。

按照“照我说的做,别照我做的做”的经典方式,以及由于不同操作系统版本之间相对路径的限制,示例使用相同的文件夹以防止本书中出错。要继续,你可以克隆或下载本书 GitHub 仓库中的所有文件和子文件夹,在 Jupyter 仪表板上创建所有文件夹和文件,或者在您的工作站上创建它们。完成后,本章的项目文件夹应该看起来如下面的截图所示:

图片

上传文件

现在我们有了项目文件夹,让我们遍历以下步骤来上传一个用于分析的文件。你必须提前从技术要求部分找到的 GitHub 仓库 URL 下载文件:

  1. 点击data文件夹名称。

  2. 点击source子文件夹名称。

  3. 点击屏幕右上角的“上传”按钮。

  4. 选择evolution_of_data_analysis.csv

  5. 点击蓝色的“上传”按钮继续。完成后,你将在仪表板上看到一个文件,如下面的截图所示:

图片

  1. 返回到notebooks文件夹,通过点击“新建”菜单创建一个新的笔记本文件。类似于hello_world示例,在子菜单中选择 Python 3 以创建默认的未命名笔记本。

如前所述,我总是在移动之前重命名未命名笔记本,所以将笔记本重命名为evolution_of_data_analysis

  1. 要从笔记本中读取文件中的数据,你必须运行几个 Python 命令。这些命令可以全部在一个单元中运行,也可以作为三个单独的单元条目运行。打开我们之前上传的 CSV 文件的命令如下:
f = open("../data/source/evolution_of_data_analysis.csv","r")
print(f.read())
f.close()

让我们逐行查看命令。首先,我们将文件打开命令的值赋给f变量,以缩短下一行中附加命令的长度。注意evolution_of_data_analysis.csv文件包含了目录路径"../data/source/",这是必需的,因为活动笔记本evolution_of_data_analysis位于不同的文件夹中。打开命令还包括一个参数r,这意味着我们只想读取文件,而不编辑内容。

第二行是通过传递f变量和read()函数来打印文件内容。这将结果显示在输出单元格中,类似于以下截图:

图片

最后一行是一个最佳实践,用于关闭文件,以避免在以后使用文件或在操作系统文件系统中出现冲突。一旦你验证可以在笔记本中看到 CSV 文件的内容,请从文件菜单中选择关闭和停止选项,返回到仪表板。

探索 Python 包

在结束本章之前,让我们探索与数据分析相关的不同 Python 包,并验证它们是否可在 Jupyter 笔记本应用程序中使用。这些包随着时间的推移而发展,并且是开源的,因此程序员可以贡献并改进源代码。

Python 包的版本将随着时间的推移而增加,具体取决于你在机器上安装condapip(包管理器)的时间。如果你在运行命令时收到错误,请验证它们是否与本书中使用的版本匹配。

当我们在未来的章节中使用它们的出色功能时,我们将更深入地介绍每个单独的包。本章的重点是验证特定的库是否可用,并且有几种不同的方法可以使用,例如检查工作站上的特定文件安装文件夹或从 Python 命令行运行命令。我发现最简单的方法是在新笔记本中运行几个简单的命令。

导航回notebooks文件夹,通过点击菜单中的新建并从子菜单中选择 Python 3 来创建一个新的笔记本文件,以创建默认的Untitled笔记本。为了保持最佳实践的连贯性,在继续之前,请确保将笔记本重命名为verify_python_packages

检查 pandas

验证每个 Python 包是否可用的步骤与代码略有不同。第一个将是pandas,这将使完成常见的数据分析技术(如数据透视、清理、合并和分组数据集)变得更加容易,而无需返回到原始记录。

要验证pandas库是否在 Jupyter 中可用,请按照以下步骤操作:

  1. In []:单元格中输入import pandas as pd

  2. 使用在安装 Python 和使用 Jupyter Notebook部分中讨论的先前方法运行单元格:

  • 从工具栏中点击按钮。

  • 从单元格菜单中选择“运行单元格”。

  • 按下 Shift + EnterCtrl + Enter 键。

  1. 在下一个 In []: 单元格中输入 np.__version__ 命令。

  2. 使用步骤 2 中推荐的方法运行单元格。

  3. 验证显示为 Out [] 的输出单元格。

pandas 的版本应该是 0.18.0 或更高。

现在,您将为本书中使用的以下每个必需的包重复这些步骤:numpysklearnmatplotlibscipy。请注意,我已经使用了每个库的常用快捷名称,以使其与行业中的最佳实践保持一致。

例如,pandas 已缩短为 pd,因此当您从每个库调用功能时,您只需使用快捷名称即可。

根据所需的分析类型、数据输入的变体以及 Python 生态系统的进步,可以使用并应该使用额外的包。

检查 NumPy

NumPy 是 Python 的一种强大且常用的数学扩展,用于对称为数组的值列表执行快速数值计算。我们将在 第三章 “NumPy 入门” 中了解更多关于 NumPy 功能的强大之处。

要验证 numpy 库是否在 Jupyter 中可用,请按照以下步骤操作:

  1. In []: 单元格中输入 import numpy as np

  2. 使用在 安装 Python 和使用 Jupyter Notebook 部分中讨论的推荐方法运行单元格:

  • 从工具栏中点击按钮。

  • 从单元格菜单中选择“运行单元格”。

  • 按下 Shift + EnterCtrl + Enter 键。

  1. 在下一个 In []: 单元格中输入 np.__version__ 命令。

  2. 使用步骤 2 中推荐的方法运行单元格。

  3. 验证显示为 Out [] 的输出单元格。

NumPy 的版本应该是 1.10.4 或更高。

检查 sklearn

sklearn 是一个用于聚类和回归分析的先进开源数据科学库。虽然我们不会利用这个库的所有高级功能,但安装它将使未来的课程更容易进行。

要验证 sklearn 库是否在 Jupyter 中可用,请按照以下步骤操作:

  1. In []: 单元格中输入 import sklearn as sk

  2. 使用在 安装 Python 和使用 Jupyter Notebook 部分中讨论的推荐方法运行单元格:

  • 从工具栏中点击按钮。

  • 从单元格菜单中选择“运行单元格”。

  • 按下 Shift + EnterCtrl + Enter 键。

  1. 在下一个 In []: 单元格中输入 sk.__version__ 命令。

  2. 使用步骤 2 中推荐的方法运行单元格。

  3. 验证显示为 Out [] 的输出单元格。

sklearn 的版本应该是 0.17.1 或更高。

检查 Matplotlib

Matplotlib Python 库包用于使用 Python 进行数据可视化和绘制图表。

要验证 matplotlib 库是否在 Jupyter 中可用,请按照以下步骤操作:

  1. In []: 单元格中输入 import matplotlib as mp

  2. 使用在安装 Python 和使用 Jupyter Notebook部分中较早讨论的首选方法运行单元格:

  • 点击工具栏中的按钮。

  • 从单元格菜单中选择“运行单元格”。

  • 按下Shift + EnterCtrl + Enter键。

  1. 在下一个In []:单元格中输入mp.__version__命令。

  2. 使用步骤 2 中讨论的首选方法运行单元格。

  3. 验证显示为Out []的输出单元格。

matplotlib的版本应该是1.5.1或更高。

检查 SciPy

SciPy是一个依赖于 NumPy 的库,它包括用于数据分析的额外数学函数。

要验证scipy库是否在 Jupyter 中可用,请按照以下步骤操作:

  1. In []:单元格中输入in import scipy as sc

  2. 使用在安装 Python 和使用 Jupyter Notebook部分中讨论的首选方法运行单元格:

  • 点击工具栏中的按钮。

  • 从单元格菜单中选择“运行单元格”。

  • 按下Shift + EnterCtrl + Enter键。

  1. 在下一个In []:单元格中输入sc.__version__命令。

  2. 使用步骤 2 中讨论的首选方法运行单元格。

  3. 验证显示为Out []的输出单元格。

scipy的版本应该是0.17.0或更高。

一旦完成所有步骤,你的笔记本应该看起来类似于以下截图:

摘要

恭喜,我们现在已经设置好了一个可以用于处理数据的环境。我们首先使用名为 Anaconda 的conda包安装程序安装了 Python 和 Jupyter 笔记本应用程序。接下来,我们启动了 Jupyter 应用程序,并讨论了如何导航仪表板和笔记本的所有功能。我们创建了一个工作目录,它可以作为所有数据分析项目的模板。

我们通过创建一个hello_world笔记本来运行我们的第一个 Python 代码,并介绍了 Jupyter 中可用的核心功能。最后,我们验证并探索了不同的 Python 包(NumPy、pandas、sklearn、Matplotlib 和 SciPy)及其在数据分析中的应用。你现在应该已经熟悉并准备好在 Jupyter 笔记本中运行额外的 Python 代码命令了。

在下一章中,我们将通过一些实践课程来扩展你的数据素养技能。我们将讨论 NumPy 的基础库,它用于分析称为数组的结构化数据。

未来阅读

这里有一些链接,你可以参考以获取更多关于本章相关主题的信息:

开始使用 NumPy

本章介绍了数据分析中最强大的 Python 库之一:NumPy。您将学习用于分析的关键函数,我们还将讨论使用 NumPy 的数组和矩阵数据结构。最后,我们将通过一些作为未来学习模块基础的实用示例进行讲解。

在本章中,我们将涵盖以下内容:

  • 理解 Python NumPy 数组及其重要性

  • 单维和多维数组之间的差异

  • 创建你的第一个 NumPy 数组

  • NumPy 和数组的实际应用案例

第五章:技术要求

这是本书的 GitHub 仓库:github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter03

您可以从以下链接下载和安装所需的软件:www.anaconda.com/products/individual

理解 Python NumPy 数组及其重要性

几门关于 NumPy 的 Python 课程专注于构建编程或统计示例,旨在为数据科学打下基础。

虽然这很重要,但我希望忠实于那些刚开始使用数据工作的人,因此重点将放在 Python 和 NumPy 在数据分析中的实际应用上。这意味着不会涵盖 NumPy 的所有功能,所以我鼓励你通过查看“进一步阅读”部分中的资源来学习更多。NumPy 库的历史演变自最初命名为数值 Python。它是由 David Ascher、Paul Dubois、Konrad Hinsen、Jim Hugunin 和 Travis Oliphant 在 2001 年作为一个开源项目创建的。根据文档,其目的是扩展 Python,以便能够操作以网格状方式组织的大量对象。

Python 不支持开箱即用的数组,但有一个类似的功能称为列表,它在性能和可扩展性方面存在限制。

对 NumPy 创建原因的进一步研究指出,在处理大量数据时需要内存和存储的效率。如今,NumPy 可以在 GitHub 的公共搜索中找到,作为数百万个 Python 项目的依赖库,包括用于人脸识别的图像处理的数千个示例。

NumPy 库全部关于数组,因此让我们了解一下数组是什么以及为什么它很重要。我参加的任何计算机科学或编程课程都包括数组。我在三十七年前,也就是在谢尔曼夫人的四年级课堂上,第一次接触到了计算机——苹果 IIe——在此之前,我就接触到了数组。

在 Apple IIe 上可运行的一种教育软件叫做Logo这是一种允许你编写简单命令来控制计算机监视器上光标移动的编程语言。为了使过程对年轻观众更具吸引力,命令允许你创建几何形状并打印出代表海龟的值。在Logo*中,数组使用list命令,该命令将一个或多个单词或数字组合成一个单一的对象,可以在同一会话中引用。你仍然可以找到可用的模拟器,允许你运行Logo编程语言,这对我是件有趣的事情,希望你也一样享受。

数组的更正式的定义是它是一个用于存储值列表或值集合(称为元素)的容器。元素必须使用适用于数组中所有值的特定数据类型来定义,并且该数据类型在创建数组期间不能更改。这听起来像是一条严格的规则,但它确实在所有数据值之间创造了一致性。使用 NumPy 库中找到的数组数据类型(称为dtype)有一些灵活性,这些数据类型被称为dtype(数据类型)。最常见的dtypeBoolean用于真/假值,char用于单词/字符串值,float用于小数,int用于整数。支持的完整数据类型列表可以在进一步阅读部分的文档中找到。

数组的例子可以是 1 到 10 的数字列表,或者如股票代码APPLIBMAMZN这样的字符列表。甚至像战舰和象棋这样的棋盘游戏也是数组的例子,其中棋子放置在棋盘上,并通过字母和数字的交互来识别。NumPy 中的数组支持复杂数据类型,包括句子,但请记住,你必须保持数据类型定义和一致,以避免错误。数组有各种形状和大小,所以让我们通过几个例子来了解一下。

单维和多维数组之间的区别

如果数组只有一个维度,它将表示一个单行或单列(但不能同时是两者)。以下例子展示了分配给名为1d_array的变量的一个一维数组:

1d_array = ([1, 2, 3, 4, 5])

一个二维数组,也称为矩阵,可以是多行和多列的任意组合。以下方程式是一个二维数组的例子:

2d_array =
([1, 'a'],
 [2, 'b'],
 [3, 'c'],
 [4, 'e'],
 [5, 'f'])

你可能已经从例子中意识到,由行和列组成的结构化数据表是一个二维数组!现在你可以看到为什么理解数组概念为对结构化数据进行数据分析奠定了基础。

一旦定义了数组,就可以在同一个会话中通过引用它来用于计算或操作,例如在改变值的顺序或根据需要替换值。数组在编程中有多种用途,所以我想要专注于与数据分析相关的特定用例。

理解数组不仅仅是简单的行和列的表格。所讨论的示例要么是一维的,要么是二维的。如果一个数组有超过一个维度,你可以通过轴(XY,或Z)来引用值。

使用numpy库包,核心功能是ndarray对象,它允许有任意数量的维度,这被称为n-维。这指的是数组在多个轴上的形状和大小。因此,具有XYZ轴的 3D 立方体也可以使用 NumPy 数组创建。散点图可视化是分析数据类型的有用方式,我们将在第九章,绘图、可视化和讲故事中介绍,并给出一些示例。

NumPy 的一些其他关键特性包括以下内容:

  • 能够对大数据集执行数学计算的能力

  • 使用运算符比较值,例如大于和小于

  • 将两个或更多数组中的值组合在一起

  • 从它们存储的方式引用序列中的单个元素

使用块来表示元素的不同类型的数组可视化表示如下图所示。以下图是一个包含三个元素的数组,也称为向量,当处理有序数时,或者当处理有序对时称为元组。任何数组的第一个元素使用索引值0引用:

图片

下面的图是一个二维数组,也称为矩阵。这个矩阵是从第一个一维数组构建的,但现在有两行三列,总共六个元素:

图片

一个n-维数组可以表示为类似于以下图的立方体。这个n-维数组继续从前两个示例构建,现在包括第三个维度或轴:

图片

我认为最好的学习方法是通过实际操作,熟悉使用命令来处理数据,所以让我们启动一个 Jupyter 笔记本,通过一些简单的示例来学习。

创建你的第一个 NumPy 数组

创建一维数组的简单示例是一个直接的命令。在将你的 Jupyter 笔记本从Untitled重命名为array_basics后,首先要做的是通过在In []命令中输入import numpy as np并将单元格运行,将numpy库导入到你的活动会话中。

我喜欢首先运行这一行以确保库已正确安装,所以如果你收到错误,请仔细检查并确保condapip已正确设置。有关帮助,请参阅第二章,Python 和 Jupyter Notebook 安装概述

接下来,你想要给数组对象分配一个变量名,这样你就可以在未来的命令中引用它。通常使用单个字符值,如ax作为数组的快捷方式,但为了便于入门,让我们使用更描述性的名称,例如my_first_array以便更容易引用。在等号右侧,我们使用np.array引用numpy方法,后跟一个括号和方括号,它们封装了每个元素的分配值。运行命令后,为了确保语法正确,最后的命令将是打印数组以确保输出与输入匹配。一旦完成,结果应该类似于以下截图:

现在我们有了可用的数组,让我们来了解一下如何验证其内容。

有用的数组函数

这里包含了一些对 NumPy 中任何数组运行以获取元数据(关于数据的数据)的有用命令。这些命令是针对名为my_first_array的变量运行的:

  • my_first_array.shape:它提供了数组的维度。

  • my_first_array.size:这显示了数组元素的数量(类似于表格中的单元格数量)。

  • len(my_first_array):这显示了数组的长度。

  • my_first_array.dtype.name:这提供了数组元素的数据类型。

  • my_first_array.astype(int):这会将数组转换为不同的数据类型——在这个例子中,将显示为int64的整数。

如果你将在 Jupyter 中运行前面的命令,你的笔记本应该看起来类似于以下截图:

一维数组的形状、大小和长度都会输出相同的结果。

要引用数组中的单个元素,你使用方括号以及一个序数整数,这被称为数组索引。如果你熟悉 Microsoft Excel 函数vlookup,要引用要检索的数据的索引的行为有类似的概念。使用 NumPy 的任何数组中的第一个元素将是0,所以如果你只想显示上一个例子中的第一个值,你将输入print(my_first_array[0])命令,这将输出屏幕上的1,如以下截图所示:

由于我们在这个例子中使用的数组具有数值,我们还可以对这些值执行一些数学函数。

注意 NumPy 中数组的默认dtypefloat,但如果您在首次创建时不定义数据类型,Python 将根据分配的值自动分配一个,或者提供错误信息。

您可以对具有dtypeintfloat的数值数组运行以下一些有用的统计函数:

  • my_first_array.sum(): 计算所有元素值的总和

  • my_first_array.min(): 提供整个数组中的最小元素值

  • my_first_array.max(): 提供整个数组中的最大元素值

  • my_first_array.mean(): 提供平均值或平均数,即元素的总和除以元素的数量

如果您在笔记本中对my_first_array运行这些统计命令,输出将类似于以下截图:

如您从这些几个示例中可以看到,NumPy 库中内置了许多有用的函数,这些函数将帮助您在分析过程中进行数据验证和质量检查。在进一步阅读部分,我放置了一个可打印的一页速查表链接,方便参考。

NumPy 和数组的实际应用案例

让我们通过一个实际案例来了解如何在数据分析中使用一维数组。这里是场景——您是一位数据分析师,想知道当前年度累计YTD)的股票代码的最高每日收盘价。为此,您可以使用数组将每个值作为元素存储,从高到低排序价格元素,然后打印第一个元素,这将显示最高价格作为输出值。

在将文件加载到 Jupyter 之前,最好检查文件内容,这支持我们在第一章中讨论的了解您的数据KYD)概念,数据分析基础。以下截图是一个逗号分隔的、具有两列的结构化数据集。该文件包含一个带有Date字段的标题行,其格式为YYYY-MM-DD,以及一个标记为Close的字段,它代表该股票代码在交易日结束时的收盘价。这些数据是从 Yahoo Business 下载的,手动删除了一些列,然后以逗号分隔的格式存储为文件。文件名代表股票代码,因此AAPL代表苹果公司,这是一家在全国证券交易商自动报价协会NASDAQ)股票交易所上市的上市公司:

第一步将是加载包含数据的文件。为了方便,我已经将此文件放置在此书的 GitHub 仓库中,因此请继续使用第二章中介绍的最佳实践创建一个新的项目文件夹,启动一个新的 Jupyter Notebook。

使用 Python 的语法是明确和区分大小写的,所以如果预期的输出不正确或收到错误信息,不要气馁。在大多数情况下,简单地更改代码就可以解决问题,然后可以重新运行命令。

对于此场景,有几个选项可以使用 NumPy 将数据加载到数组中。

手动分配数组值

第一种选项是显式地将值手动分配给数组,如下面的截图所示:

此选项适用于小型数据集、测试语法或其他特定用例,但在处理大数据或多个数据文件时将不切实际。我们通过仅输入源文件中的十个值来使用此选项进行了一些捷径。由于所有股票价格都是数值型并且具有一致的数据类型,我们可以使用默认dtypefloat的一维数组。

重新生成此选项的步骤如下:

  1. 启动 Jupyter 并创建一个新的 Python 笔记本。

  2. 为了保持最佳实践的一致性,在继续之前,请确保将笔记本重命名为highest_daily_closing_stock_price_option_1

  3. 输入以下命令以在笔记本中导入numpy库,输入In []:,并运行单元格:

In []: import numpy as np
  1. 在下一个输入单元格中,添加以下命令使用快捷键np将值赋给名为input_stock_price_array的 NumPy 数组变量。通过运行单元格继续,这将不会产生输出,Out []
input_stock_price_array = np.array([142.19,148.26,147.93,150.75,153.31,153.8,152.28,150,153.07,154.94])
  1. 在下一个输入In []:单元格中,添加以下命令将值赋给名为sorted_stock_price_array的 NumPy 数组变量,并运行该单元格。与之前类似,结果将不会产生输出,Out []
sorted_stock_price_array = np.sort(input_stock_price_array)[::-1] 
  1. 输入以下命令,这些命令使用print()函数显示每个数组变量的结果:
print('Closing stock price in order of day traded: ', input_stock_price_array)
print('Closing stock price in order from high to low: ', sorted_stock_price_array) 

按下Enter键创建新行,以便在运行单元格之前添加第二行命令。

  1. 确认输出单元格显示Out []
  • 将会有两行输出,第一行是原始值数组。

  • 第二行输出是数组中值的排序列表。

  1. 输入以下命令以使用print()函数显示结果:
print('Highest closing stock price: ', sorted_stock_price_array[0]) 
  1. 确认输出单元格显示Out []。输出应声明最高收盘股票价格:154.94

从这些步骤中需要记住的关键概念是,你加载一个初始的股票价格值数组,并将其命名为 input_stock_price_array。这一步是在导入 NumPy 库并将其分配给 np 快捷方式之后完成的,这是一种最佳实践。接下来,你从原始数组创建一个新的数组,命名为 sorted_stock_price_array,并使用 NumPy 的 sort() 函数。sort() 函数的好处是它会自动将原始数组的元素从低到高排序。由于本场景的目标是获取最高值,我们在函数中添加了 [::-1] 参数,这将按降序排序值元素。

从原始数组创建一个新的数组有助于使你的分析更容易重复和重用。操作顺序在这个过程中变得至关重要,因此你必须按顺序执行步骤以获得正确的结果。

为了验证结果,我们添加了一个额外的步骤来一起打印两个数组,以便直观地比较元素并确认新数组是按降序排序的。由于原始任务是获取 最高 股票价格,所以最终步骤是打印排序数组中的第一个元素,其索引值为 0。如果步骤没有错误地执行,你将看到数据样本中的最高收盘股票价格,即 154.94

直接分配数组值

与手动在数组中分配值相比,一个更可扩展的选项是使用另一个名为 genfromtxt() 的 NumPy 命令,该命令在 numpy 库中可用。使用此函数,我们可以直接从读取文件的行和列中分配数组元素。genfromtxt() 函数有几个参数,可以支持通过隔离所需的特定列及其数据类型来处理数据的结构。

genfromtxt() 函数有多个必需和可选参数,你可以在 进一步阅读 部分找到它们。对于我们的示例,让我们浏览一下回答我们业务问题所需的那些参数:

  • 第一个参数是文件名,它被分配给我们上传的文件,命名为 AAPL_stock_price_example.csv

  • 第二个参数是分隔符,由于输入文件就是这样结构的,所以是逗号。

  • 下一个参数是通知函数我们的输入数据文件有一个标题,通过将 names= 参数分配给 True

  • 最后一个参数是 usecols=,它定义了从哪个特定列读取数据。

根据 genformtxt() 函数的帮助信息,当传递一个值给 usecols= 参数时,默认情况下第一列总是被分配为 0。由于我们需要文件中的 Close 列,我们将参数值更改为 1 以匹配我们在输入文件中找到的顺序。

一旦使用genfromtxt()函数将input_stock_price_array加载到内存中,一个快速的大小检查将验证元素数量与源文件中的行数相匹配。请注意,标题行将不包括在内。在下面的屏幕截图中,您可以看到对手动数组选项的一些修改,但一旦数组用值填充,剩余的步骤非常相似。我在print()函数中添加了[:5]以显示前五个元素,使其更容易比较源输入数组和新的排序数组:

图片

重现此选项的步骤如下:

  1. 启动 Jupyter 并创建一个新的 Python 笔记本。

  2. 为了保持最佳实践的一致性,在继续之前,请确保将笔记本重命名为highest_daily_closing_stock_price_option_2

  3. AAPL_stock_price_example.csv文件上传到 Jupyter 笔记本。

  4. In []:单元格中输入import numpy as np

  5. 运行单元格。

  6. 在下一个In []:单元格中输入input_stock_price_array = np.genfromtxt('AAPL_stock_price_example.csv', delimiter=',', names=True, usecols = (1))

  7. 运行单元格。

  8. 在下一个In []:单元格中输入input_stock_price_array.size

  9. 验证输出单元格显示Out []:。当排除标题行时,行数为229

  10. 在下一个In []:单元格中输入sorted_stock_price_array = np.sort(input_stock_price_array)[::-1]

  11. 运行单元格。

  12. 在下一个In []:单元格中输入print('Closing stock price in order of day traded: ', input_stock_price_array[:5]) print('Closing stock price in order from high to low: ', sorted_stock_price_array[:5])

  13. 运行单元格。

  14. 验证输出单元格显示Out []

  • 将会有两行输出,第一行是原始值数组。

  • 第二行输出是数组的排序值列表。

  1. 在下一个In []:单元格中输入print('Highest closing stock price: ', sorted_stock_price_array[0])

  2. 运行单元格。

  3. 验证输出单元格显示Out []:。输出应声明Highest closing stock price: 267.100006

使用循环给数组赋值

另一种可能使用更多代码但更有灵活性来控制填充数组过程中的数据质量的方法是使用循环。使用这种方法需要了解一些概念,但我认为这将有助于理解这一点,并适用于进一步的学习练习。

过程的总结如下:

  1. 将文件读入内存

  2. 遍历每个单独的记录

  3. 从每条记录中删除一个值

  4. 将每个值分配给一个临时数组

  5. 清理数组

  6. 以降序对数组进行排序

  7. 打印数组中的第一个元素以显示最高价格

在这个过程的最后几个步骤应该看起来很熟悉,因为它们是从上一个选项中重复的,其中我们清理数组,对其进行排序,然后打印第一个元素。重现此选项的完整步骤如下:

  1. 启动 Jupyter 并创建一个新的 Python 笔记本。

  2. 为了保持最佳实践的一致性,在继续之前,请确保将笔记本重命名为 highest_daily_closing_stock_price_option_3

  3. AAPL_stock_price_example.csv 文件上传到 Jupyter 笔记本。

一定要将源 CSV 文件上传到正确的文件位置,这样你就可以在 Jupyter 笔记本中引用它。

  1. 在笔记本输入 In []: 中输入以下命令以导入 numpy 库,并运行该单元。运行此命令后不会有输出:
In []: import numpy as np
  1. 在我们可以填充它之前,通过清理所有值来初始化数组。运行此命令后不会有输出:
In []: temp_array = []
  1. 在以下代码块中,我们必须在循环中连续执行多个命令。顺序很重要,Jupyter 会自动缩进,当你输入 In []: 单元时。我包括了注释以更好地理解代码。运行此命令后不会有输出:
#A. Read the file into memory
 with open('AAPL_stock_price_example.csv', 'r') as input_file:

     #B. load all the data into a variable
     all_lines_from_input_file = input_file.readlines()

     #C. Loop through each individual record 
     for each_individual_line in all_lines_from_input_file:

         #D. Strip out a value from each record
         for value_from_line in \
           each_individual_line.rsplit(',')[1:]:

             #E. Remove the whitespaces from each value
             clean_value_from_line = \
                            value_from_line.replace("\n", "") 

             #F. Assign each value to the new array by element
             temp_array.append(clean_value_from_line)
  1. temp_array 被填充了元素之后,一个快速的 print() 函数识别出另一个需要执行的数据清理步骤。在下一个 In []: 单元中输入以下命令并运行该单元:
print(temp_array[:5])
  1. 确认输出单元显示 Out [],它将类似于以下截图。数组包括一个标题行值 Close,价格值周围有单引号:

  1. 源文件中的标题行已被包含在我们的数组中,这可以通过将数组赋值给自己并使用 delete() 函数删除第一个元素来轻松移除。运行此命令后不会有输出:
temp_array = np.delete(temp_array,0)
  1. 使用 size() 函数通过添加以下命令来确认数组的大小与原始源输入文件匹配,并运行该单元:
temp_array.size
  1. 确认输出单元显示 Out [],它将类似于以下截图。排除标题行时,行数为 229

  1. 数组的每个元素周围都有单引号。这可以通过使用 astype() 方法的简单命令来修复,将数组的 dtype 转换为 float,因为股票价格是十进制数值。运行此命令后不会有输出:
input_stock_price_array = temp_array.astype(float)
  1. 打印新数组中的前几个元素以验证数组已清理元素:
print(input_stock_price_array[:5])
  1. 验证数组现在只包含十进制格式的数值,并且引号已被移除,类似于以下截图:

  1. 最后几个步骤是之前练习的重复。我们使用 sort() 函数按降序排序数组,并通过传递参数 [::-1] 来从高到低排序。在下一个 In []: 单元中输入以下命令并运行该单元。运行此命令后不会有输出:
sorted_stock_price_array = np.sort(input_stock_price_array)[::-1] 
  1. 通过在sorted_stock_price_array中引用第一个排序元素并使用print()函数输入命令来运行单元格,打印数组中的前几个元素以显示最高价格:
print('Closing stock price in order of day traded: ', input_stock_price_array[:5])
print('Closing stock price in order from high to low: ', sorted_stock_price_array[:5]) 
  1. 确认输出单元格显示Out []
  • 将会有两行输出,第一行是原始值数组。

  • 第二行输出是数组值的排序列表。

这将类似于以下截图:

图片

  1. 要查看最高价格,请使用print()函数,并对排序后的数组使用[0]命令来显示第一个值:
print('Highest closing stock price: ', sorted_stock_price_array[0])
  1. 确认输出单元格显示Out [],这将类似于以下截图。输出应声明最高收盘价:267.100006

图片

摘要

恭喜,我们现在已经学会了如何使用numpy库的关键特性以及一些实际应用案例。我们首先通过提供它们在计算机科学和编程语言中根植数十年的例子来了解数组及其重要性。我们还学习了结构化数据的基础,它使用数组的概念,通过解释单维和多维数组的区别以及我们通常如何将它们识别为具有列和行的表格。

在解释了历史和理论之后,我们学习了如何创建 NumPy 数组,并探讨了一些有用的函数。我们通过将股票价格加载到数组中作为实际应用案例来结束本章,以展示如何通过使用一些用于数据分析的 NumPy 命令来回答特定问题。通过理解数据类型如何影响数据分析以及 KYD 概念的重要性,本章加强了数据处理能力。

在下一章中,我们将通过一些实际操作课程来扩展你的数据处理能力,这些课程将使用pandas库中的数据结构 DataFrame。

进一步阅读

这里有一些链接,你可以参考,以了解更多关于本章相关主题的信息:

创建您的第一个 pandas DataFrame

在本章中,我们将探讨使用文件系统和格式进行核心数据分析技能。我们将使用 Python OS 和字符串库探索用于操作来自源文件(如 逗号分隔值CSV)、可扩展标记语言XML)和 JavaScript 对象表示法JSON))的文本和数值数据的不同文件格式。您将了解 pandas DataFrame 是什么以及如何从文件源创建 DataFrame 进行数据分析。

我们在本章中将涵盖以下主题:

  • 操作表格数据的技术

  • 理解 pandas 和 DataFrame

  • 处理基本数据格式

  • 数据字典和数据类型

  • 创建您的第一个 DataFrame

第六章:技术要求

这是本书的 GitHub 仓库链接:github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter04

您可以从以下链接下载并安装所需的软件:www.anaconda.com/distribution/

操作表格数据的技术

现在,我们已经通过在 第三章 “使用 NumPy 入门” 中使用 NumPy 库更好地理解了数组数据结构,我们可以现在扩展我们的数据分析专业知识。我们将通过处理表格数据并专注于 Python 中可用的强大库 pandas 来做到这一点,该库可用于我们的 Jupyter 笔记本。

pandas 库扩展了我们分析结构化数据的能力,并于 2008 年由 Wes McKinney 作为 Python 库引入。McKinney 认识到通过使用库来扩展 Python 语言的力量,以及通过在 Python 中执行整个数据分析工作流程(无需切换到更特定领域的语言,如 R)来填补数据准备与数据洞察之间存在的差距的必要性。

pandas Python 库的名称取自术语 panel data(由 McKinney 提出),通过缩短和合并术语得到 panda。面板数据定义为可以在一段时间内用多个维度的值进行测量的观测值,在统计研究和研究论文中非常常见。我也见过面板数据被称为纵向数据、事实面板数据或横截面时间序列数据。面板数据以表格形式呈现,有行和列,并且有几种不同类型,如平衡的、不平衡的、长的、短的、固定的和旋转的。

这些面板数据类型都是基于数据集数量的精确表示方式。观察值的总数(数据行)通常用字母N表示。时间元素的单位,如年、月、季度或日期,通常用大写或小写的字母T表示。维度或变量(数据列)可以用每个实体的特定字母表示,例如xz。所测量的量可以用一个或多个变量表示,通常分配给y。你应该能够用描述性术语总结任何面板数据,以便消费者在查看或以表格形式处理它之前理解。

根据数据集的不同,维度可能随时间变化,也可能不变化,因此可能使用不同的字母,如k来表示这种区别。

面板数据的一个例子是三个上市公司的 3 天每日收盘价,如下表所示:

日期 股票代码 收盘价
12/2/2019 ABC $50.21
12/3/2019 ABC $52.22
12/4/2019 ABC $51.01
12/2/2019 DEF $24.22
12/3/2019 DEF $26.22
12/4/2019 DEF $29.50
12/2/2019 GHI $61.22
12/3/2019 GHI $65.33
12/4/2019 GHI $75.00

另一个例子是过去三个月(按月)按 ZIP 代码划分的最低、最高和平均温度,如下表所示:

月份 ZIP 代码 最低温度 最高温度 平均温度
六月 19901 75 88 82
七月 19901 77 90 84
八月 19901 68 85 77
六月 08618 76 89 83
七月 08618 78 91 85
八月 08618 69 86 78
六月 18940 74 87 81
七月 18940 76 89 83
八月 18940 67 84 76

一个不平衡的面板将是一个其中至少有一个维度值缺失的值。一个平衡的面板将是一个包含所有维度元素且跨越所有时间段的包容性数据集。

我们从第一章,《数据分析基础》中了解到,数据以各种形状和大小存在,因此将数据以表格形式结构化将是分析过程的第一步,但在许多情况下,并非最终步骤。例如,在下面的表格中,我们有过去三年按城市划分的总销售额的汇总交叉表。

这类汇总数据可以识别为交叉表,这使得数据消费者能够快速识别按城市和按年份的最高和最低销售额。在这种情况下,这将是 2019 年的纽约,销售额为$120,000,以及 2017 年的波士顿,销售额为$25,000:

城市 2017 2018 2019
费城 $50,000 | $75,000 $100,000
纽约 $35,000 | $65,000 $120,000
波士顿 $25,000 | $40,000 $ 90,000

如果这个数据表只有有限行和列,这将是你的分析的最终步骤,因为你可以在不进行额外操作的情况下快速回答大多数商业问题,例如哪个城市的销售额最高。然而,如果记录数量增加到显示超过 100 个城市,我们将年份增加到最近 10 年,或者你想要获取更多细节以更好地理解销售额,通过增加维度(如产品、店铺编号、交易日期、一天中的时间和支付方式)来细分金额,会怎样呢?

增加列的数量将使回答简单的商业问题变得具有挑战性和耗时,例如,所有城市所有年份的平均销售额是多少?因此,分析的可扩展性取决于你操纵数据的能力,而不仅仅是接收数据的方式。

数据分析的最佳实践是从结果出发开始分析。因此,对于这个例子,我们想要生成的输出表将类似于以下表格,我们将列转置为行,以便更容易进行额外分析,并且我们准备好处理更大的数据量:

大规模的数据量是一个主观术语,但所使用的技术应该支持分析数百万或数十亿行数据。你将需要超出个人工作站可用 RAM 和 CPU 限制的额外基础设施。

城市 年份 销售额
费城 2017 $50,000
2018 $75,000
2019 $100,000
纽约 2017 $35,000
2018 $65,000
2019 $120,000
波士顿 2017 $25,000
2018 $40,000
2019 $90,000

从前面的输出中,我们可以看到:

  • 数据结构类似于前面输出表的第一大优势是,每个列都有一个单一的数据类型,这通常被称为维度或轴。

  • 第二个优势是,由于每个维度都可以被视为具有相同数据类型的独立值数组,因此统计分析变得更加容易,可以使用 NumPy 进行计算,如第三章所述,NumPy 入门

  • 第三个优势是能够按表中的任何字段排序,而不用担心每行/元组中的数据值变得错位或不一致。

  • 保持数据的完整性可以建立对分析过程的信任,并确保你的分析将是准确的。

建议你在数据操作过程中分解每个步骤,以便使过程可重复——例如,如果你被要求在几个月后重复这个过程,或者如果你必须调试底层源数据中存在的异常,这是非常常见的。

理解 pandas 和 DataFrames

既然我们对表格数据有了更好的理解,我们也提供了一些关于面板数据以及 pandas 库创建原因的背景信息,那么让我们通过一些 pandas 的例子来深入了解,并解释 DataFrame 的使用方法。

pandas 库是一个强大的 Python 库,用于更改和分析数据。pandas DataFrame 是库中的一个功能,定义为具有标记轴(行和列)的二维、大小可变的、可能异构的表格数据结构。DataFrame 是一个二维数据结构——也就是说,数据以表格形式按行和列对齐。众所周知,pandas DataFrame 由三个主要组件组成:数据、行和列。作为一个视觉学习者,我创建了一个以下图表的例子,我们现在可以一起看看:

图片

DataFrame 可以与电子表格进行比较,例如 Microsoft Excel 或 Google Sheets,任何 关系数据库管理系统RDBMS)中找到的单个 SQL 表,或者甚至是一个 QlikView 数据QVD)文件。之前的例子都包含一个 标题行 的共同元素,它定义了数据的标签和对齐方式,(每行被识别为一个单独的记录), 对每个字段值的结构进行分类,以及包含数值和/或文本值的 数据

在我们的例子中,每一行都包含一个 ID 字段的身份记录,但这在 pandas DataFrame 中不是必需的。DataFrame 在 Python 中被视为对象,并支持直接将数据从文件或 SQL 源加载到内存中,以便进行额外的操作和分析。使用 DataFrame 的一些关键好处包括以下内容:

  • 它允许您将所有源文件转换为可读的数据对象,以便更容易地进行合并和分析。

  • 它提供自动或定义的索引,以帮助查找值或从 DataFrame 中选择交叉选择,这通常也称为数据切片。

  • 每一列都可以被视为一个单独的 NumPy 数组,这些数组可以具有多种数据类型。

  • 它在修复数据对齐和缺失数据元素方面表现卓越,这些元素显示和引用为 非数字NaN)。

  • 它允许在不返回每个数据集的记录源的情况下进行数据旋转和重塑。

  • 使用单个 Python 命令添加、删除或更改数据很容易,这样可以加快对一个或多个数据源的分析。

  • 允许对指标进行聚合,如 Group By,以及执行 summinmax 等其他计算。

  • 允许对一个或多个 DataFrame 进行合并、排序、连接和过滤。

  • 它可以扩展以支持可重复的工作流程分析。例如,以下伪代码步骤在 Jupyter 笔记本中很容易复制:

  1. 导入 pandas 库。

  2. 将源文件加载到新的 DataFrame 中。

  3. 创建一个第二个 DataFrame,它会从原始 DataFrame 中删除重复的行或列。

  4. 创建汇总指标。

  5. 将第二个 DataFrame 保存为新的文件。

我喜欢使用pandas和 DataFrame 的原因是,作为数据分析师,内置命令的灵活性。让我们通过几个例子来了解一下。要从头开始创建一个包含有限记录的 DataFrame,你可以简单地使用几个命令来添加它们:

  1. 要加载pandas,你只需将以下命令添加到你的 Jupyter 笔记本中并运行该单元格。你可以自由地跟随创建自己的笔记本;我已经在 GitHub 上添加了一个副本以供参考:
In[]: import pandas as pd
  1. 接下来,我们有几个产品——abc——以及销售数量,我们将这些输入数据分配给一个名为product_data的变量:
product_data = {
   'product a': [13, 20, 0, 10],
   'project b': [10, 30, 17, 20],
   'project c': [6, 9, 10, 0]
}

当手动加载数据时,请注意方括号的位置,用于封装每个列标签的值,以及数组必须具有相同的长度。

  1. 然后,我们想要通过调用命令并使用pd快捷方式来引用pandas库以及DataFrame()命令来加载数据框。我们将 DataFrame 输入数据作为第二个变量分配,以便更容易引用,称为purchase_dataIn[]单元格将如下所示:
purchase_data = pd.DataFrame(product_data)
  1. 为了验证结果,你可以运行head()函数来显示前五行数据,使用以下命令:
purchase_data.head()

执行前面的代码后,我们可以看到:

  • 输出结果将类似于以下截图,其中各个数组的副产品已被转换为一个带有标签标题行的 DataFrame,并且每个销售数量值都进行了对齐,以便于参考。

  • 注意,在product a左侧创建了一个新的索引列,并分配了从0开始的顺序值:

  1. 拥有索引值对于参考很有用,但如果我们希望在创建 DataFrame 时定义它们,我们可以在创建过程中包含一个相关命令,如下所示:
purchase_data = pd.DataFrame(product_data, index=['Ronny,' 'Bobby,' 'Ricky,' 'Mike'])
  1. 现在,如果你运行head()命令来显示结果,你将看到每个索引号分配了特定的值,其显示方式如下所示:

  1. 要从 DataFrame 中选择特定的行,你使用loc函数通过索引检索结果,如下所示:
purchase_data.loc['Ronny']

这将产生一个输出,如下面的截图所示,其中显示分配给Ronny的行的各个值以汇总格式呈现,每列和值都由一个包含索引名称和值的数据类型描述的行表示(dtype: int64):

一旦索引被标记,你必须使用单引号中的名称来访问loc[]函数;然而,你可以使用iloc[]ix[]函数通过数字引用行索引,第一行从0开始。因此,purchase_data.iloc[0]purchase_data.ix[0]都将返回与前面截图相同的结果。

处理基本数据格式

通过更好地理解使用pandas库和数据框(DataFrames)功能的力量,让我们来探索处理多种数据格式,包括来自 CSV、JSON 和 XML 等源文件。我们在第一章,“数据分析基础”中简要介绍了这些不同的文件格式作为理解结构化数据的一部分,因此让我们深入探讨每种源文件类型,并学习一些处理它们时的基本技能。

CSV

首先,我们有 CSV,这在我的职业生涯的大部分时间里一直是行业标准。通常通过.csv文件扩展名来识别 CSV 文件;然而,随着时间的推移,你会发现这并不总是如此,用于分隔数据记录中值的分隔符也不一定是逗号。CSV 文件之所以受欢迎,是因为它们具有可移植性,并且从创建它们的源系统中技术上是中立的。

这意味着 CSV 文件可能是由任何编程语言创建的,例如 Python、C++或 Java。同样,创建 CSV 文件的相同操作系统,如 Windows、Unix、Linux 或 macOS,也不需要读取文件。这有助于 IT 专业人士在需要时在组织内外移动数据,从而帮助其被广泛采用。

由于其持久性,你会发现多年来已经采用了许多不同的变体和标准。例如,记录可能包含或不包含标题行,并且字段/列之间的分隔符可以是制表符、管道(|)或任何其他 ASCII 或 UTF-8 字符值。

美国信息交换标准代码ASCII)是计算机用来以数字方式解释键盘值的一种常见字符编码标准。Unicode 转换格式UTF-8)是通用的字符编码标准,与 ASCII 向后兼容。这两个标准都很受欢迎,并且被广泛使用。

为正确的 CSV 格式定义了一些规则,但随着你继续使用它们,你可能会发现更多例外。由 Y. Shafranovich 在 2005 年发表的《互联网协会》中发布的部分规则如下:

  • CSV 文件中的每条记录应该是独立的,并包含一个换行符(CRLF)来标识每一行。

  • 在最后一条记录后添加换行是可选的,但某种类型的文件结束符EOF)将有助于在系统间读取数据。

  • 标题行是可选的,但应包含与相应记录级别数据相同的字段/列数。

  • 每条记录应使用一致的分隔符,例如逗号(,)、分号(;)、竖线(|)或制表符。

  • 在每个字段值之间包含双引号是可选的,但建议这样做以避免错位或混淆,尤其是在读取包含字段值本身中的逗号的大型描述性文本数据时,例如Also, Stacy enjoys watching movies

  • 每个字段之间的前导和尾随空格也是可选的,但应在整个文件中保持一致。

  • CSV 文件的大小会有所不同,但可能相当大,并且取决于数据的密度(不同值的数量、行数和字段数)。

根据操作系统,例如 Linux,CSV 文件可能只包含每行的 换行符LF),而不是 回车符CR)。

在下面的屏幕截图中,我包含了一些 CSV 文件中样本行的示例,所有这些行都包含完全相同的信息,但使用不同的格式来分隔文件中的字段:

图片

消费或生成 CSV 相对于其他格式的最大优势是能够在其他用于数据分析的工具之间共享。例如,电子表格解决方案,如 Excel,可以轻松读取 CSV 文件,而无需转换文件或使用第三方扩展。然而,一个缺点是丢失了每列定义的数据类型,这可能导致在分析中错误地表示值。例如,列中的10值可能代表布尔标志或来自网站的用户点击次数。

CSV

XML 文件格式在 20 世纪 90 年代被引入作为一种标准格式。我还记得在我职业生涯的早期,XML 被提议作为 CSV 的替代品,甚至可以替代数据库作为数据存储库。XML 作为一种解决方案,对开发者来说非常灵活,可以用来创建网络应用,类似于 CSV,它被用来在组织内外移动数据。XML 是开源的,并且有一个由万维网联盟WC3)组织维护的明确定义的标准。XML 文件格式的几个关键特性如下:

  • 它通常与文件扩展名 .xml 相关联。

  • 第一行应包含一个声明,其中包含编码细节和.xml版本,例如<?xml version = "1.0" encoding="UTF-8" ?>

  • 它在每个元素周围使用与 HTML 标签代码类似的标签,使用开始标签 <>/>

  • 它包含元素,这些是结构化数据中定义的字段或列。

  • 它包含属性,这些是每个定义元素中的数据值。

  • 包含 文档类型定义DTD)是可选的,但推荐这样做,因为它提供了详细信息并有助于定义元素应该如何使用,以及数据类型。

下面的屏幕截图显示了示例 XML 文件。在这里,我将 evolution_of_data_analysis.csv 转换为 XML 格式,并显示了一些示例记录:

图片

虽然 XML 的一个缺点是文件大小较大,因为需要在每个元素中添加标签和定义,但使用 XML 格式的优点是能够支持数据层次结构和定义的架构。让我们分析这两个要点。

数据层次结构

数据层次结构是数据字段或记录的明确和一致的分组。层次结构可能是显而易见的——例如,一个儿子有一个父亲和一个母亲——但从数据的角度来看,这种关系必须被定义。在 XML 文件格式中,您使用一个称为 XML 树的概念。树是由 XML 文件中具有定义关系的元素定义的。在 Evolution of Data Analysis.xml 文件的情况下,每个里程碑都有相关的详细信息分组在一起。现在,我们可以轻松地识别 John von Neumann / array 的里程碑事件是在 1945 年创建的,以及所有其他标记的辅助元素,如 <Decade><Milestone Title><Milestone Event><Why Important><Reference><People Process or Technology Tag>。这种层次结构关系通常被称为 父子 关系,其中每个缩进的元素都是父元素 Evolution_of_Data_Milestone 的子元素。

定义架构

定义架构意味着数据元素还将包括元数据(关于数据的资料),以帮助每个元素和属性的符合性。这个概念在大多数关系型数据库管理系统(RDBMS)中是必需的,但 XML 提供了将 DTD 文件包含在一个或多个 XML 文件中的概念。文件扩展名是 .xsd,它应该与每个 XML 文件相匹配。

XSD 文件的内容可能很复杂且非常密集,这取决于在 XML 文件中找到的记录的复杂性和在消费 XML 数据时定义刚性结构的需求。例如,为每个元素定义的数据类型可以帮助您更好地理解在分析期间如何使用数据。例如,如果使用 type="xs:decimal",您就知道每个元素中的属性值必须包含数值,任何文本值都不应该存在。另一个有用的架构定义是 use="required" 的元素定义,这意味着特定的元素必须始终具有值,并且不应该包含任何空/空属性。

关于这个主题的更多细节可以在 W3C 网站上找到,您可以在本章的“进一步阅读”部分找到。

JSON

JSON 是另一种用于系统间数据通信的开源文件标准。它由道格拉斯·克罗克福德于 2001 年左右创建,旨在通过一个称为无状态的概念来改善计算机和网页浏览器之间的通信。这意味着你的计算机的网页浏览器,即所谓的客户端,不必等待服务器响应,反之亦然。这也就是所谓的表征状态转移REST)架构,在网页、API 和现代技术中非常常见,因为它可以扩展以支持数百万并发用户。

一旦 REST 成为流行的网络架构,寻找更快、更高效的通信协议的需求推动了 JSON 数据的采用,它既可以流式传输,也可以持久化为文件。由于许多网站使用 JavaScript 以及 JavaScript 友好的标记,这也增加了 JSON 的流行度。

与 XML 和 CSV 类似,JSON 不仅对人类可读,而且对许多不同类型的计算机系统和技术,如 Python,也是可读的。这也意味着 JSON 不是二进制文件格式,这意味着它不需要对文件进行编译以便计算机使用。我将 JSON 作为“数据分析的演变及其重要性”部分中的里程碑,在第一章,“数据分析基础”中包括,因为它对推进我们使用数据通信的贡献。以下截图显示了 JSON 格式示例,这与之前XML部分中的 XML 格式示例非常相似,因为你现在可以看到数据通过花括号({})组织并按记录分组,以封装原始 CSV 文件中的每一行。每个使用花括号分组的都被识别为一个对象:

图片

在理解 JSON 数据时,一个重要的概念是它起源于 XML,但简化了许多在 XML 格式中可能存在的复杂性。像 XML 一样,它受益于定义数据层次结构的能力,并包含一个定义良好的模式,支持称为读取时模式的概念。

在具有定义良好模式的传统解决方案中,生产者在任何数据加载或系统间传输之前被迫建立模式。这个过程需要专业知识,并在数据摄入期间增加了额外步骤,延迟了数据向消费者的交付。有了 JSON 和读取时模式的概念,生产者可以同时发送数据以及所有元数据。所有细节,如字段名称、每个字段的dtype(数据类型),以及在某些情况下,完整的数据字典,都将包括在内。提供这一级别的细节有助于数据消费者更好地理解每个元素和属性之间的关系。

你会在很多 JSON 格式的数据中找到name: value对的概念,这在之前的屏幕截图中的示例中也使用了。这个概念允许在识别每个记录中的字段的同时分配值,而不是将记录拆分到多行中。每个字段名称位于冒号(:)的左侧,而值位于冒号的右侧。

每个name: value关系由逗号分隔,许多示例将具有唯一的记录标识符,这有助于执行一对一关系的分析。因此,你可以在 JSON 结构中嵌套许多不同的关系,并且仍然有方法来识别name: value对属于哪个记录。如果需要在 JSON 文件中存储值数组,它们使用方括号([])来定义值列表。

定义一个模式迫使数据具有超出观察到的控制项和上下文。它消除了对数据属性假设,并有助于解释数据值应该如何用于分析。例如,值20191219可以很容易地理解为整数值,或者可能是以YYYYMMDD格式存储的12/19/2019日期的表示。如果没有定义的模式来参考,以及关于如何以及为什么应该使用该字段的详细信息,你的数据分析可能会出现错误。

数据字典和数据类型

在整本书中,我将继续强调拥有数据字典以帮助分析数据的重要性。与迄今为止我们发现的所有数据一样,数据字典将具有各种形状和大小。这意味着它可能记录在源文件之外,这在帮助页面、维基或博客中很常见,或者在我们讨论的 XML 和 JSON 文件中记录在源数据内。

数据的定义和文档化将帮助你理解数据,但不会是成为数据集领域专家所需的所有方法。领域专业知识来自于理解数据如何使用以及底层源数据的业务或目的的经验。我们在第一章,“数据分析基础”,中讨论了这些概念,探讨了了解数据(KYD)和拥有数据字典如何有助于学习更多关于底层数据集的信息。

数据分析和 KYD 概念应该在整个数据分析过程中得到应用,所以请确保检查数字并验证结果是否符合数据的定义,以建立对洞察力的信任和信心。

数据字典在遗留系统、关系数据库管理系统(RDBMS)和传统的企业数据仓库EDW)中很常见。通常会有一个数据目录可用,在许多情况下,它们是构建不同系统之间通信数据管道所必需的。在某些情况下,数据字典是作为监管要求的一部分或作为受控企业政策的一部分所必需的。

在现代系统中,应用程序编程接口APIs)已成为元数据的中心存储库和事实上的数据字典,因为 JSON 是一种流行的通信工具,其中定义了模式并且应该有良好的文档。然而,在实践中,我发现文档是由程序员为程序员编写的,因此它可能无法满足完全理解数据和在分析期间回答所有业务问题的所有需求。

将数据字典版本化作为主数据管理MDM)或数据治理解决方案的一部分也很常见。在这些版本中,您将揭示数据背后的是什么为什么的细节。例如,一个字段可能被定义为非活动状态但仍然可用,因此它变得稀疏,因为用于填充它的应用程序/系统已经改变。

拥有如此详细的程度可能有助于识别数据差距或更好地理解如何通过在不同时间段结合两个不同领域的值来构建数据桥梁,以进行准确的历史分析。我曾经与一位客户合作,他正在用咨询硬件和软件替换一个耗资数百万美元的大型企业遗留系统。咨询时间按小时计算,每周有数十名专家前往客户现场。

在项目中有一个关键时刻,确定将所有遗留供应链、会计和人力资源细节从旧系统迁移到新系统是不切实际且成本过高的。为了避免延误,我们提出了一个分析解决方案,其中每天将遗留系统数据和新的系统数据合并在一起。构建了一个滚动时间窗口逻辑,因此 7 年后,遗留数据将不再用于分析,但在那个时间段内,包括不同字段和记录的两种系统的混合将用于分析。

对于此类解决方案,拥有数据字典是必不可少的,并且需要提供额外的文档以确保受众了解数据来源,这取决于报告和分析的时间段。该文档的一部分需要不同字段和数据类型变化的细节。某些系统将允许不同数据类型的混合,或者在 Python 中,将默认为特定的数据类型。

只需记住,你可能需要在多个来源之间转换数据类型,尤其是在不同系统和文件格式之间混合时。例如,在 JSON 中,定义为 real 的数字在 Python 中被称为 float。如果在加载数据时遇到转换数据类型的问题,可能需要回到数据源提供者并请求以更易于消费的格式重新发送。

随着你继续提高你的数据素养,你需要理解不同的技术和数据格式会导致不同的数据类型,这需要转换以确保对数据的准确分析,尤其是来自多个来源的数据。

创建我们的第一个 DataFrame

在我们开始一些动手示例之前,以下是一些在 pandas 中可以运行的实用命令:

  • pd.read_csv('inport_filename.csv', header=1):直接从 CSV 文件读取数据到 pandas DataFrame

  • my_df.to_csv('export_filename'):直接将 DataFrame 导出为 CSV 文件到您的工作站

  • my_df.shape:提供 DataFrame 的行数和列数

  • my_df.info():提供关于 DataFrame 的元数据,包括每列的数据类型

  • my_df.describe():包括统计细节,其中包含计数、平均值、标准差std)、最小值、最大值和百分位数(25th、50th 和 75th)的列

  • my_df.head(2):显示 DataFrame 的前两条记录

  • my_df.tail(2):显示 DataFrame 的最后两条记录

  • my_df.sort_index(1):按轴上的标签排序——在这个例子中,按列标签标题从左到右字母顺序排序

  • my_df.isnull():显示所有具有 True/False 指示器的行列表,如果任何列的值是空的

我们的第一个例子将从 CSV 文件加载数据到具有管道(|)分隔符的 pandas DataFrame 中,并运行一些前面的命令:

  1. 启动 Jupyter 并创建一个新的 Python 笔记本。

  2. 为了保持最佳实践的一致性,在继续之前,请确保将笔记本重命名为 exploring_the_pandas_library

  3. In []: 单元格中输入 import pandas as pd

  4. 运行单元格。运行单元格后不会显示任何输出。

  5. 在下一个 In []: 单元格中输入 my_df = pd.read_csv('evolution_of_data_analysis.csv', header=0, sep="|")

  6. 运行单元格。运行单元格后不会显示任何输出。

  7. 在下一个 In []: 单元格中输入 my_df.shape

  8. 确认输出单元格显示 Out []。将显示 (42, 7),这告诉你有 42 行和 7 列,如下截图所示:

截图

  1. 在下一个 In []: 单元格中输入 my_df.info()

  2. 运行单元格。

  3. 确认输出单元格显示 Out []。将会有多行,包括所有七列的数据类型,如下截图所示:

截图

  1. 在下一个 In []: 单元格中输入 my_df.describe()

  2. 运行单元格。

  3. 验证输出单元格显示Out []。将有多个输出行,其中一列的标题为Year,如下面的截图所示。将显示来自Year字段的统计值,包括countmeanmax

图片

  1. my_df.head(2)输入到下一个In []:单元格中并运行该单元格。

  2. 验证输出单元格显示Out []

  • 输出应包括第一列的索引,起始行为0,如下面的截图所示。

  • 所有七列都将显示,包括源文件的前两行:

图片

  1. my_df.tail(2)输入到下一个In []:单元格中并运行该单元格。

  2. 验证输出单元格显示Out []。输出应包括第一列的索引,起始行为40,如下面的截图所示。所有七列都将显示,包括源文件中的最后两行:

图片

  1. my_df.sort_index(1)输入到下一个In []:单元格中并运行该单元格。

  2. 验证输出单元格显示Out []。输出应包括第一列的索引,起始行为0,如下面的截图所示。所有七列都将显示,但列的顺序已改为从左到右按字母顺序排序,从Decade开始,以Year结束:

图片

在下一个示例中,让我们通过探索pandas中的一些功能来回答一些来自数据的企业问题。第一个问题是每个十年发生了多少个里程碑事件?要回答这个问题,我们需要使用groupby功能,所以让我们通过以下步骤来提供答案。

重新生成此示例的步骤如下:

  1. 启动 Jupyter 并创建一个新的 Python 笔记本。

  2. 为了保持最佳实践的一致性,在继续之前,请确保将笔记本重命名为exploring_the_pandas_library_example_2

  3. import pandas as pd输入到In []:单元格中并运行该单元格。

  4. my_df = pd.read_csv('evolution_of_data_analysis.csv', header=0, sep="|")输入到下一个In []:单元格中并运行该单元格。

  5. my_df.head(2)输入到下一个In []:单元格中并运行该单元格。

  6. 验证输出单元格显示Out []

  • 输出应包括第一列的索引,起始行为0

  • 所有七列都将显示,包括源文件的前两行。

  1. my_df.groupby(['Decade']).agg({'Year':'count'})输入到In []:单元格中并运行该单元格。

  2. 验证输出单元格显示Out []

  • 输出将显示 10 行数据,2 列。

  • 第一列的标题行将是Decade,第二列将是Year

  • 结果将与以下截图匹配:

图片

在前面的屏幕截图中,我们遵循了之前的步骤将 CSV 文件作为名为my_df的 DataFrame 加载。为了验证 DataFrame 已正确加载,我们运行了head()函数,并包括了参数2以限制在笔记本中显示的行数。最后一个命令是运行groupbyDecade列进行分组,并将其与聚合结合,以计算Milestone Event字段/列的值。我们现在可以回答一些关于这个数据集的问题,例如,在 2000 年代发生了 14 个里程碑事件,或者第一个有里程碑事件的十年是 1940 年代,因为这是第一个有值的行。

摘要

恭喜,你现在已经使用pandas库创建了你第一个 DataFrame!我们通过介绍结构化表格数据的概念以及通过转置和交叉数据来操作它的不同技术开始了这一章。更重要的是,我们讨论了为什么数据应该以表格形式存在的重要性。然后我们介绍了pandas库,并定义了 DataFrame,并展示了在数据分析过程中这个强大功能为您带来的许多好处。在处理基本数据格式时,我们通过 CSV、XML 和 JSON 文件格式的细节介绍了可用的不同数据格式。在我们通过创建第一个 DataFrame 来结束这一章之前,我们讨论了数据字典的重要性以及不同数据类型如何提高您的数据素养,以及为什么它们在数据分析工作流程完成前后都很重要。

在下一章,第五章,在 Python 中收集和加载数据,我们将向您介绍如何使用 SQL 从数据库加载数据,并继续使用pandas和 DataFrame 的功能。

进一步阅读

在 Python 中收集和加载数据

本章将通过教授您如何使用 SQLite 数据库来使用和访问数据库,解释 SQL 是什么以及为什么它对数据分析很重要。将提供关系数据库技术的概述,以及关于数据库系统的深入信息,以帮助提高您与专家沟通时的数据素养。您还将学习如何在 Jupyter Notebook 中运行 SQL SELECT查询,以及如何将它们加载到 DataFrames 中。基本统计、数据血缘和元数据(关于数据的数据)将使用pandas库进行解释。

本章我们将涵盖以下主题:

  • SQL 和关系数据库简介

  • 从 SQL 到 pandas DataFrames

  • 解释您数据的数据

  • 数据血缘的重要性

第七章:技术要求

这是本书的 GitHub 仓库:github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter05

您可以从以下链接下载和安装所需的软件:www.anaconda.com/products/individual

SQL 和关系数据库简介

我们现在处于本书的这样一个阶段,我的职业生涯开始于与数据库和 SQL 一起工作。结构化查询语言SQL)是几十年前作为一种与存储在表中的结构化数据进行通信的手段而创建的。多年来,SQL 已经从针对底层数据库技术的多种变体中演变而来。例如,IBM、Oracle 和 Sybase 都对其 SQL 命令有所变体,这在其客户中建立了忠诚度,但在更换供应商时需要做出改变。国际标准化组织ISO)和美国国家标准协会ANSI)标准的采用有助于定义今天普遍使用的标准。

到目前为止,本书中的所有结构化数据示例都集中在单个表或文件上。关系数据库通过使用主键和外键的概念,解决了在多个表中存储数据并保持它们之间一致性的问题:

  • 主键是用于表示每个表中单个不同记录或元组的唯一值(通常是整数)。

  • 外键将是一个表中的字段,它引用另一个表的主键。

这种关系定义了在所有数据中保持一致性的一个或多个表之间的完整性。由于存储和连接数据的概念是抽象的,这使得它可以应用于许多不同的数据主题。例如,您可以创建一个数据库来存储制造公司的销售数据,网站的用户点击,或金融服务公司的股票购买。正因为这种多功能性,SQL 仍然是顶级编程语言,也是数据分析必备的技能。

SQL 是为了与存储在具有定义模式的数据库表中的数据进行通信而创建的。数据库模式就像一个蓝图,在数据加载之前定义了存储数据的结构。这个定义包括规则、条件和表中每个字段的特定数据类型。数据库技术的基石是由 Codd 博士在 1970 年创立的,它是 数据分析演变 的重要里程碑,我在 第一章,数据分析基础 中定义了这一点。将数据持久化在定义的列和行中作为结构化关系中的表,展示了 Codd 博士对这一技术和数据的贡献的遗产。他与 Ralph Kimball 和 Bill Inmon 等人的贡献在多年中创造了新的行业和职业。如果你遇到一个 企业数据仓库 (EDW),你可以肯定它使用 Kimball 或 Inmon 方法作为标准。他们的影响,定义了与数据工作的新技能,不容小觑。我对那些推动支持所有数据技术和概念的技术和概念发展的人怀有深深的感激。

关系数据库的定义是一个庞大的主题,所以我将专注于构建你的数据素养和从使用 SQL 消费数据进行分析的相关内容。在处理任何 关系数据库管理系统 (RDBMS) 时,关键概念始于如何与托管数据库的系统或服务器进行通信。大多数系统支持使用 ODBC 驱动程序,该驱动程序处理网络上的身份验证和通信。开放数据库连接 (ODBC) 是一个用于在分析工具(例如 Jupyter Notebook)和数据存储之间发送和接收数据的常用标准。大多数大型企业关系数据库系统支持 ODBC 连接以与数据库进行通信。

传统上,这被称为客户端-服务器架构,其中你的本地计算机被称为客户端,数据库的位置由一个或多个服务器管理。当我是一名顾问时,我工作中遇到的最常见的企业关系数据库管理系统是 Microsoft SQL Server、Oracle、IBM DB2、MySQL 和 PostgreSQL。

可能需要在你的工作站上安装和配置 ODBC 驱动程序,以便与客户端-服务器架构进行通信。

现在,开源和供应商数据库产品都有其他版本,但许多确实应该支持 SQL 或其变体。例如,Apache 的 HiveQL 与 ASCI SQL 非常相似,但它在Hadoop 分布式文件系统HDFS)上运行,而不是在数据库上。在我们的示例中,我们将使用 SQLite,这是一个基于文件的数据库,你可以本地安装或通过 ODBC 连接。SQLite 是开源的,跨平台的,这意味着我们可以在任何操作系统上安装它,根据他们的下载页面,它被誉为世界上部署和使用最广泛的数据库引擎,你可以在进一步阅读部分找到它。

一旦建立了连接,通常需要用户 ID 和密码,这些控制你可以执行的操作以及你可以访问的表。如果你自己安装了数据库,你就是数据库的所有者,可能拥有系统管理员权限,这让你可以完全访问创建、删除和读取任何表。如果你是客户端,数据库管理员DBA)将负责为你的用户 ID 设置访问和权限。

我认为即使到现在,SQL 之所以成为一门流行的语言,是因为使用它所需的曲线学习。根据我的经验,许多商业用户和数据分析师即使没有计算机科学背景,也会觉得其语法直观易懂。SQL 代码易于阅读,并且可以迅速理解预期的结果。它还支持即时满足,一旦优化性能,即使数据量很大,几个命令也能在不到一秒内产生结果。

例如,假设我想知道 2018 年苹果股票的最高收盘价。即使没有真正理解这些数据是如何或在哪里存储的所有细节,这一行代码的语法也容易理解:

SELECT max(closing_price) FROM tbl_stock_price WHERE year = 2018

让我们逐步分析这段代码,并分解其关键组件:

  • 首先,我将保留词大写了,这些保留词在支持 ISO 标准/ASCI SQL 的任何关系数据库管理系统(RDBMS)中都是通用的。

  • SELECT命令指示代码从FROM语句之后定义的表中以行和列的形式检索数据。

  • SELECTFROM保留词之间是max(closing_price)命令。这是使用 SQL 中可用的max()函数从closing_price字段检索最大或最大值。无论数据中是否存在重复值,max 函数都只返回一行和一个值。

  • 代码中的FROM部分让 SQL 解释器知道紧随其后的是引用一个表或对象。在这个例子中,我们正在寻找tbl_stock_price表中的记录。

  • SELECT SQL 语句中的WHERE子句通过减少行数到特定的条件来限制数据,该条件由等号右侧的特定字段yearvalue定义,即2018

SELECT 是最常用的 SQL 命令,具有许多不同的使用场景和复杂度级别。我们只是触及了表面,你可以在进一步阅读部分找到更多资源。

SQL 对大小写不敏感,但所引用的表和字段可能因所使用的 RDBMS 而异。保留字之间需要有空格,但通常在表或字段名称中找不到空格。相反,下划线或破折号更为常见。

从 SQL 到 pandas DataFrame

现在我们对 SQL 和关系数据库有了背景知识,让我们下载 SQLite 数据库文件的本地副本,设置连接,并将一些数据加载到 pandas DataFrame 中。为此示例,我提供了名为 customer_sales.db 的数据库文件,所以请确保在 GitHub 存储库中事先下载它。

为了让你了解这个数据库文件,并支持我们在第一章“数据分析基础”中学到的了解你的数据KYD)概念,我们创建了三个名为 tbl_customerstbl_productstbl_sales 的表格。这将是任何拥有客户购买产品并在任何时间段内产生销售的公司的一个简单示例。数据存储和连接的视觉表示,通常称为ERD(实体关系图),如下所示:

图片

在前面的图中,我们可以看到三个表格的视觉表示,每个表格的列名定义在每个框的左侧,而每个列的数据类型则紧挨着其右侧。每个表格的主键通过名称后缀_ID来标识,并在每个表格的第一行文本上使用粗体表示。主键通常具有整数数据类型,这里也是如此。

tbl_sales 表包括两个这样的字段,Customer_IDProduct_ID,这意味着它们被分类为外键。表之间的线条加强了它们之间的关系,这也指示了如何将它们连接起来。看起来像鸟爪的小线条告诉消费者这些表是以一对一的关系定义的。在这个例子中,tbl_sales 将会有许多客户和许多产品,但每个 Customer_IDtbl_customers 中只会分配一个值,而 tbl_products 将只会为每个 Product_ID 分配一个值。

现在我们对数据有了更多了解,让我们启动一个新的 Jupyter 笔记本,并将其命名为 retrieve_sql_and_create_dataframe。为了创建连接并使用 SQLite,我们必须使用代码导入一个新的库:

  1. 要加载 SQLite 数据库连接,你只需在你的 Jupyter 笔记本中添加以下命令并运行该单元格。你可以自由地跟随操作,创建自己的笔记本(我已经在 GitHub 上放置了一个副本以供参考):
In[]: import sqlite3

sqlite3 模块是 Anaconda 分发版自带安装的。有关设置环境的帮助,请参阅 第二章,Python 和 Jupyter Notebook 安装概述

  1. 接下来,我们需要将一个名为 conn 的连接分配给变量,并指向数据库文件的位置,该文件名为 customer_sales.db。由于我们在之前的 In[] 行中已经导入了 sqlite3 库,我们可以使用这个内置函数与数据库通信:
In[]: conn = sqlite3.connect('customer_sales.db')

请确保将 customer_sales.db 文件复制到正确的 Jupyter 文件夹目录,以避免连接错误。

  1. 下一个需要导入的库应该是非常熟悉的,这样我们就可以使用 pandas,从而使代码如下所示:
In[]: import pandas as pd
  1. 要运行一个 SQL 语句并将结果分配给一个 DataFrame,我们必须运行这一行代码。pandas 库包含一个 read_sql_query() 函数,这使得使用 SQL 与数据库通信变得更容易。它需要一个连接参数,我们在前面的步骤中将其命名为 conn。我们将结果分配给一个新的 DataFrame 作为 df_sales 以便更容易识别:
In[]: df_sales = pd.read_sql_query("SELECT * FROM tbl_sales;", conn)
  1. 现在我们已经将结果放入 DataFrame 中,我们可以使用所有可用的 pandas 库命令来处理这些数据,而无需返回到数据库。要查看结果,我们只需运行 head() 命令针对这个 DataFrame 并使用此代码:
In[]: df_sales.head()

输出将类似于以下截图,其中 tbl_sales 表已经被加载到一个带有标签的标题行 DataFrame 中,索引列位于左侧,起始值为 0

  1. 要对 DataFrame 中的值进行排序,我们可以使用 sort_values() 函数并包含一个字段名称参数,它将默认为升序。让我们首先按日期排序结果,以查看数据库中首次记录的销售日期:
In[]: df_sales.sort_values(by='Sale_Date')

输出将类似于以下截图,其中 DataFrame 输出现在按 Sale_Date 字段从 1/15/20156/9/2019 排序。注意 Sale_ID 的差异,它是不连续的:

  1. 要限制显示的数据,我们可以使用 DataFrame.loc 命令根据标题行的标签来隔离特定的行或列。要检索第一行可用的数据,我们只需运行这个命令针对我们的 DataFrame 并引用索引值,它从 0 开始:
In[]: df_sales.loc[0]

输出将类似于以下截图,其中一条记录以序列的形式显示,多列转置为多行:

使用这种方法,你必须知道你正在通过索引查找哪个特定记录,这反映了数据是如何从 SQL 语句中加载的。为了确保数据库表之间的一致性,你可能想在将数据加载到 DataFrame 中时包含一个 ORDER BY 命令。

  1. 要限制显示的数据,我们可以使用嵌套命令根据条件隔离特定的行。你可以使用这些数据解决的业务任务包括识别销售量高的客户,以便我们可以亲自感谢他们。为此,我们可以通过特定值过滤销售数据,并仅显示满足或超过该条件的行。在这个例子中,我们将high分配给一个任意数字,所以任何Sales_Amount超过 100 的记录都将使用此命令显示:
In[]: df_sales[(df_sales['Sales_Amount'] > 100)]

输出将类似于以下截图,其中根据条件显示单个记录,因为只有一个记录的Sales_Amount大于100,即Sale_ID等于4

  1. 限制结果的一个例子是查找分配给 DataFrame 中特定字段的特定值。如果我们想更好地理解这些数据,我们可以通过查看Sales_Quantity字段来查看哪些记录只购买了一个产品:
In[]: df_sales[(df_sales['Sales_Quantity'] == 1)]

输出将类似于以下截图,其中根据条件显示多个记录,其中Sales_Quantity等于1

这些步骤定义了分析工作流程的最佳实践。检索 SQL 结果,将它们存储在一个或多个 DataFrame 中,然后在你的笔记本中进行分析是常见且被鼓励的。根据数据量,在数据库之间迁移数据可能需要大量的计算资源,所以请留意你有多少可用内存以及你正在处理的数据库有多大。

数据关于数据解释

现在我们已经更好地理解了如何使用 Python 和 pandas 处理 SQL 数据源,让我们探索一些基本统计量以及数据分析的实际应用。到目前为止,我们主要关注描述性统计与预测性统计。然而,我建议在充分理解描述性分析之前,不要进行任何数据科学的预测性分析。

基本统计量

描述性分析基于过去已经发生的事情,通过分析数据的数字足迹来获得洞察力,分析趋势,并识别模式。使用 SQL 从一个或多个表中读取数据支持这一努力,这应该包括基本的统计和算术。数据结构化和规范,包括每列定义的数据类型,一旦你理解了一些关键概念和命令,这种类型的分析就会变得更容易。SQL 和 Python 中都有许多统计函数可用。

我已经在这个表中总结了几个对您的数据分析至关重要的函数:

统计量 描述 最佳用途/用例 SQL 语法 pandas 函数
计数 不论数据类型,一个值出现的次数 查找表的大小/记录数 SELECT Count(*) FROM table_name df.count()
计数(不重复) 不论数据类型,一个值出现的不同次数 移除重复值/验证用于数据类别中的唯一值 SELECT Count(distinct field_name) FROM table_name df.nunique()
求和 对数值数据类型的值的整体或总计聚合 查找总人口或衡量金钱的数量 SELECT Sum(field_name) FROM table_name df.sum()
平均值 来自两个或更多数值数据类型的算术平均值 值的总和除以值的计数 SELECT AVG(field_name) FROM table_name df.mean()
最小值 字段中值的最低数值 查找最低值 SELECT MIN(field_name) FROM table_name df.min()
最大值 字段中值的最高数值 查找最高值 SELECT MAX(field_name) FROM table_name df.max()

在 SQL 中,我最常用的统计度量是 计数,即统计每个表中记录的数量。使用此函数有助于验证你正在处理的数据量与源系统、数据生产者和业务赞助者一致。例如,业务赞助者告诉你他们使用数据库来存储客户、产品和销售,并且他们有超过 30,000 名客户。让我们假设你运行以下 SQL 查询:

SELECT count(*) from customers

有 90,000 个结果。为什么会有如此大的差异?首先的问题可能是:你是否使用了正确的表?任何数据库都是灵活的,因此它可以由 DBA 根据应用和业务需求进行组织,因此活跃客户(购买了产品并创建了销售数据的客户)可能存储在不同的表中,例如 active_customers。另一个问题是:是否有字段用于标识记录是否活跃?如果有,那么该字段应该包含在 SELECT 语句的 WHERE 部分中,例如,SELECT count(*) from customers where active_flag = true

使用count()函数进行分析的第二个优点是为自己设定预期,了解每个查询返回结果所需的时间。如果你在产品、客户和销售表上运行count(*),检索结果所需的时间将根据数据量以及 DBA 如何优化性能而变化。表格有形状,这意味着行数和列数在它们之间会有所不同。它们也可以根据其预期用途而增长或缩小。例如,sales表是事务性的,因此行数会随着时间的推移而显著增加。我们可以将事务表归类为深度,因为列数最少,但行数会增长。例如,customersproducts表被称为参考表,它们在形状上较宽,因为它们可能有数十个列,但行数与事务表相比要少得多。

行数和列数众多且具有密集独特值的表格会占用更多磁盘空间,并且处理时需要更多的内存和 CPU 资源。如果sales表有数十亿行,那么通过SELECT count(*) from sales来计算行数可能需要数小时等待响应,并且会被管理员/IT 支持团队所劝阻。我曾与一个数据工程团队合作,他们能够在 100 亿条记录的表中在 10 秒内检索 SQL 结果。这种响应时间需要开发者的专业知识和对表进行配置以支持超快响应时间的管理权限。

在处理count()函数时,另一个有效的观点是了解频率与独特值之间的区别。根据你针对哪个表执行计数函数,你可能只是在计算记录出现的次数,或者记录的频率。以 30,000 个客户为例,如果count(customer_id)count(distinct customer_id)的结果之间有差异,我们知道计数记录包括了重复的客户。这取决于你进行的分析,可能不是问题。如果你想知道客户购买任何产品的频率,那么count(customer_id)将回答这个问题。如果你想知道有多少客户购买每种产品,使用distinct将提供更准确的信息。

sum()函数,简称求和,是描述性分析中用于统计分析的另一个常用度量。计数与求和之间的一个关键区别是,求和需要一个数值来计算准确的结果,而计数可以对任何数据类型进行。例如,你不能也不应该对customers表中的customer_name字段求和,因为数据类型被定义为字符串。如果你将其定义为整数,技术上可以对customer_id字段求和,但这会提供误导性的信息,因为这不是该字段的预期用途。像count一样,sum是一个聚合度量,用于将特定字段(如sales_amount或销售表中的数量)中找到的所有值相加。

在 SQL 中使用sum()函数很简单。如果你想知道所有时间的总和而没有约束或条件,可以使用以下语法:

 SELECT sum(field_name) from table_name

然后,你可以添加一个条件,例如只包括活跃客户,通过包含带有flag字段的WHERE子句,其语法如下:SELECT sum(field_name) from table_name WHERE active_flg = TRUE

我们将在第八章理解连接、关系和聚合中揭示更多高级功能,例如使用 SQL 进行聚合。

均值或平均函数是另一个非常常用的统计函数,对于数据分析非常有用,并且使用 SQL 编写命令很容易。平均数是所有值的总和除以计数,语法为SELECT avg(field_name) from table_name

计数值的分母是使用频率/发生次数与不同值相比,因此在运行 SQL 命令之前你应该理解表是如何填充的。例如,一个销售表是基于交易的,有多个客户和产品,所以平均数会与产品表或客户表的平均数不同,因为每条记录都是唯一的。

minmax函数在 SQL 中也非常有用且易于解释。内置函数是min()max(),它们从数据集中返回最小数值以及最大或最高值。从你的表中理解的一个好业务问题是 2018 年的最低和最高销售额是多少?SQL 中的语法如下:

SELECT min(sales_amount), max(sales_amount) from sales_table where year = 2018

了解每个客户和产品在所有时间段内的销售范围,这些信息将非常有用。

在对数据运行这些统计函数时,一个重要的因素是要理解值是空的还是通常所说的空值。在 SQL 中,NULL代表无物和值的不存在。在 RDBMS 中,空值是 DBA 为每个表定义架构时的一个规则。在通过为每个字段定义数据类型创建列的过程中,有一个选项允许空值。根据用例的不同,允许在数据库表设计时使用空值的原因也各不相同,但对于分析来说,重要的是要了解它们是否存在于你的数据中以及它们应该如何被处理。

让我们从我们的customers表中的一个例子开始,其中一个字段,如第二行地址允许NULL,这是常见的。为什么这是常见的?因为第二行地址字段是可选的,而且在许多情况下甚至没有被使用,但如果你是一家需要物理邮寄营销材料或发票给客户的公司呢?如果数据录入总是需要值,那么在数据库中的第二行地址字段中不必要地填充值,这是低效的,因为它需要更多的时间为每个客户输入值,并且需要更多的存储空间。在大多数情况下,在大规模企业系统中强制输入值会创建较差的数据质量,这随后需要时间来修复数据,或者在处理数据时造成混淆,尤其是在处理数百万客户的情况下。

元数据解释

元数据通常是指关于数据源描述性信息。在元数据分析中,一个关键概念与理解数据库中存在空值相关。从数据分析的角度来看,我们需要确保我们理解它对我们分析的影响。在 Python 和其他编程语言如 Java 中,你可能看到返回的单词NaN。这是“非数字”(Not a Number)的缩写,有助于你理解你可能无法对这些值执行统计计算或函数。在其他情况下,例如 Python,NaN值将具有特殊函数来处理它们,如下所示:

  • 在 NumPy 中,使用nansum()函数

  • 使用 pandas 的isnull()函数

  • 在 SQL 中,根据你使用的 RDBMS 使用is nullisnull

由于你正在测试一个条件是否存在,你也可以包括NOT关键字来测试相反的情况,例如,Select * from customer_table where customer_name is NOT null

理解空值和NaN归结为 KYD 以及你正在处理的数据源元数据。如果你无法访问数据库系统以查看元数据和底层架构,我们可以使用 pandas 和 DataFrames 来获取关于 SQL 数据的一些见解。让我们通过一个例子来操作,将单个表从数据库加载到笔记本中的 DataFrame,并运行一些元数据函数以获取更多信息。

首先,创建一个新的 Jupyter 笔记本,并将其命名为test_for_nulls_using_sql_and_pandas

  1. 与先前的例子类似,要加载 SQLite 数据库连接,你只需在你的 Jupyter 笔记本中添加以下命令并运行该单元格:
In[]: import sqlite3
  1. 接下来,我们需要将一个连接分配给名为 conn 的变量,并指向数据库文件的位置,该文件名为 customer_sales.db。由于我们在先前的 In[] 行中已经导入了 sqlite3 库,我们可以使用这个内置函数与数据库进行通信:
In[]: conn = sqlite3.connect('customer_sales.db')
  1. 如以下代码所示导入 pandas 库:
In[]: import pandas as pd
  1. 使用 read_sql_query() 函数,我们将结果分配给一个新的 DataFrame,命名为 df_customers,以便更容易识别:
In[]: df_customers = pd.read_sql_query("SELECT * from tbl_customers;", conn)
  1. 要查看结果,我们只需运行以下代码中的 head() 命令来针对这个 DataFrame:
In[]: df_customers.head()

输出将类似于以下截图,其中 tbl_customers 表已被加载到 DataFrame 中,带有标签的标题行,索引列位于左侧,起始值为 0

图片

  1. 我们可以使用以下命令来分析 DataFrame 并轻松地识别任何 NULL 值。isnull() pandas 函数在整个 DataFrame 中测试空值:
In[]: pd.isnull(df_customers)

输出将类似于以下截图,其中 DataFrame 将返回每行和每列的 TrueFalse 值,而不是单元格中的实际值:

图片

几条命令,我们就学会了如何与数据库进行通信,并识别存储在表中的数据的一些重要元数据。为了继续提高我们的数据素养,让我们通过了解数据血缘来理解数据是如何填充到数据库中的。

数据血缘的重要性

数据血缘是指追踪数据集来源以及其创建方式的能力。对我来说,这是一个有趣的话题,因为它通常需要调查系统生成数据的历史,识别其处理方式,并与生产和使用数据的个人合作。这个过程有助于提高你的数据素养,即阅读、编写、分析和用数据辩论的能力,因为你能够了解数据对组织的影响。数据对于业务功能,如生成销售是否至关重要,或者它是为了合规目的而创建的?这些问题应该通过更多地了解数据的血缘来回答。

从经验来看,追踪数据血缘的过程涉及到直接与负责数据的人员进行工作会话,并揭示任何如从 SQL 到 pandas DataFrames部分中展示的 ERD(实体关系图)或帮助指南等文档。在许多情况下,随着时间的推移而成熟的企业的系统可用的文档可能不会反映你在分析数据时看到的细微差别。例如,如果在一个之前不存在表单的现有表中创建了一个新字段,那么历史数据将会有NULLNaN值,直到数据录入开始的时间点。

数据血缘可能会迅速变得复杂,如果不进行适当的记录,则需要时间来解开并需要多个资源来揭示细节。当涉及多个系统时,与主题专家SMEs)合作将加速流程,这样你就不必逆向工程数据流中的所有步骤。

数据流

数据流是数据血缘的一个子集,通常是大组织内部更大数据治理策略的一部分,因此可能已经存在一些工具或系统,这些工具或系统能够直观地表示数据是如何处理的,这通常被称为数据流。一个假设的数据流图示例如下所示,其中我们查看迄今为止我们在练习中处理的一些数据。在这个图中,我们有tbl_customers表从我们的 SQLite 数据库中填充的逻辑表示。我已经将输入和输出记录为第一阶段到第四阶段:

图片

输入阶段

首先,我们有输入阶段,它被标识为移动应用Web 应用客户端 PC系统。这些系统已经创建了输出到多种文件格式的数据。在我们的例子中,这些数据是批量处理的,其中数据文件被保存并发送至下一阶段。

数据摄入阶段

数据摄入阶段是处理多个文件,如customers.jsoncustomers.xml的地方。因为这个图是逻辑图而不是高度技术性的图,所以省略了处理数据摄入背后使用的技术细节。数据摄入也被称为ETL,即提取、转换和加载的缩写,这是由数据工程团队或开发者自动化和维护的。

在这个 ETL 过程中,我们可以看到一个中间步骤称为tbl_stage_customers,这是一个在源文件和数据库中的目标表之间处理数据的中间表。此阶段还包括一个ODBC连接,其中客户端 PC系统可以直接访问tbl_customers表以插入、更新和删除记录。

在学习更多关于数据流的过程中,务必询问表是否使用逻辑删除而不是物理删除行。在大多数情况下,不支持直接从表中删除行,因此使用布尔数据类型列来指示记录是否由系统或用户激活或标记为删除。

数据源阶段

第三阶段被称为数据源,定义为tbl_customers表。向开发人员或数据库管理员提出的一些问题如下:

  • 你对这个数据的保留策略是什么/数据保留多长时间?

  • 这个表中每天平均填充了多少条记录?

  • 他们能否提供一些元数据,例如每个字段的行数、列数和数据类型?

  • 这个表包括主键和外键在内的依赖/连接是什么?

  • 这个表多久备份一次,我们是否应该注意可能影响分析的系统停机时间?

  • 这个表/数据库是否存在数据字典?

数据目标阶段

第四阶段被称为数据目标,它帮助数据分析师理解从源表的下游依赖关系。在这个例子中,我们有一个销售报告compliance_feed.json文件和Jupyter Notebook。需要揭示的有用信息包括合规数据发送的频率以及数据消费者是谁。

如果你的分析时间与数据源阶段的 数据馈送时间不一致,这可能变得很重要。对分析结果的信任以及能够论证你的分析是完整和准确的,来自于理解时间问题以及你解决和匹配多个数据目标输出之间计数的能力。

业务规则

数据血缘的另一个重要点是揭示业务规则、查找值或映射参考源。业务规则是一个抽象概念,有助于你理解在数据处理过程中应用到的软件代码。例如,当移动应用的用户点击提交按钮时,会创建一个新的customers.json文件。业务规则也可以更复杂,例如tbl_stage_customers表不会在tbl_customers中填充记录,直到所有源文件接收完毕并且每天凌晨 12 点(东部标准时间)运行批处理。业务规则可以在创建数据库表时明确定义,例如在列上定义主键的规则,在网页表单或移动应用程序中编码。

应该将记录这些业务规则包括在你的方法论中,以支持你的分析。这有助于你通过验证业务规则的存在或识别与关于源数据的假设相矛盾的反常值来从数据分析中得出见解。例如,如果你被告知一个数据库表被创建为不允许特定字段中的NULL,但你最终发现它存在,你可以与数据库管理员(DBA)一起审查你的发现,以揭示这种情况是如何发生的。这可能是创建了一个业务异常,或者是在表已经填充之后才实施了业务规则的执行。

理解业务规则有助于识别数据缺口并在分析过程中验证准确性。如果这个表的平均每日记录量连续多日降至零记录,那么可能是在数据摄取阶段的第 2 阶段出现了问题,或者可能只是假日,没有收到和处理客户记录。

在任何情况下,学习如何向主题专家提出这些问题并验证数据血缘将增强你对分析的信心,并赢得数据生产者和消费者双方的信任。

现在你已经理解了所有这些概念,让我们回顾一下本章中我们正在处理的数据血缘——customer_sales.db

  1. 在这个数据库的输入阶段,为了示例目的,手动创建了三个源 CSV 文件。每个源表都与一个名为tbl_customerstbl_productstbl_sales的 CSV 文件一一对应。

  2. 数据摄取阶段,每个文件都是通过几个 SQL 命令导入的,这为每个表创建了模式(字段名称、定义的数据类型和连接关系)。这个过程通常被称为 ETL,其中源数据被摄取并持久化为数据库中的表。如果需要修改源文件和目标数据库表之间的任何变化,应该记录业务规则以帮助提供数据生产者和消费者之间的透明度。对于这个例子,源数据和目标数据是一致的。

  3. 在这个例子中,数据源阶段将是customer_sales.db。现在,这成为从数据库流向分析以及任何报告的数据的黄金副本。

  4. 在我们的例子中,目标阶段将是 Jupyter 笔记本和为分析创建 DataFrame。

虽然这是一个只有几个步骤的小例子,但这些概念适用于具有更多数据源和用于自动化数据流的技术的大规模企业解决方案。我通常在开始任何数据分析之前绘制数据血缘的阶段,以确保我理解整个流程。这有助于与利益相关者和 SMEs 沟通,以确保从数据源中获得见解的准确性。

摘要

在本章中,我们涵盖了一些关键主题,帮助你通过学习与数据库交互和使用 SQL 来提高数据素养。我们了解了 SQL 的历史以及为在数据库中存储结构化数据奠定基础的人。我们通过一些示例介绍了如何将 SQL SELECT 语句中的记录插入到 pandas DataFrame 中进行分析。

通过使用 pandas 库,我们学习了如何排序、限制和限制数据,以及基本的统计函数,如计数、求和和平均值。我们介绍了如何在数据集中识别和处理 NaN(即空值),以及分析过程中数据来源的重要性。

在我们接下来的章节中,我们将探讨时间序列数据,并学习如何使用额外的 Python 库来可视化你的数据,以帮助提高你的数据素养技能。

进一步阅读

以下是一些链接,你可以参考这些链接以获取本章相关主题的更多信息:

第八章

第二部分:数据发现解决方案

在本节中,我们将通过处理时间序列数据来学习如何可视化数据以进行分析。然后,我们将学习如何使用 SQL 和 Python 中的 DataFrames 来清理和合并多个数据集。之后,我们将回到数据可视化,了解数据讲述的最佳实践。到本节结束时,你将理解描述性分析的基础。

本节包括以下章节:

  • 第六章,可视化和时间序列数据处理

  • 第七章,探索、清理、精炼和合并数据集

  • 第八章,理解连接、关系和聚合

  • 第九章,绘图、可视化和讲述故事

可视化和处理时间序列数据

无论数据源来自文件还是数据库,我们现在已经定义了一个可重复的分析工作流程。这个工作流程用于将数据加载到数组或 DataFrame 中,然后通过运行一些 Python 命令并使用相应的库来回答业务问题。

这个过程到目前为止一直很有效,是提高我们如何处理数据技能的必要步骤,这最终提高了数据素养。现在,我们将再迈出一步,帮助你通过可视化数据来传达分析。在本章中,我们将学习如何创建可以支持结构化数据的视觉元素。我们将通过揭示数据可视化创建的基本原理来分解图表的解剖结构。使用 Python 中可用的绘图功能,你将使用matplotlib库创建你的第一个时间序列图表。

本章将涵盖以下主题:

  • 结果导向的数据建模

  • 图表解剖和数据可视化最佳实践

  • 比较分析

  • 曲线的形状

让我们开始吧。

第九章:技术要求

你可以在github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter06找到这本书的 GitHub 仓库。

你可以从www.anaconda.com/products/individual下载并安装所需的软件。

结果导向的数据建模

我们在第五章“在 Python 中收集和加载数据”中提供的数据建模介绍,让我们了解了关系数据库以及可以对结构化数据执行的基本统计。在这些例子中,我们学习了数据之间的关系以及数据可以从数据生产者的角度进行建模。数据生产者负责以结构化的方式存储数据,以确保数据的完整性保持一致。在前一章中,我们也学习了如何使用实体关系图ERD)来定义表之间的关系。在本章中,我们将从数据消费者的角度应用这些相同的概念。因此,我们将专注于创建新的数据关系,使分析更容易。这个概念是报告领域的一次演变,催生了一个新兴行业,通常被称为商业智能BI)和分析

介绍维度和度量

分析用数据建模意味着我们正在从源表构建新的关系,目的是为消费者回答业务问题。创建一个具有这种关注点的新数据模型可以将你的分析扩展到单个 SQL SELECT语句之外,这些语句一次运行一个以回答一个问题。相反,使用新派生的数据模型进行分析将仅使用几个甚至一个表来回答数十个问题。这是如何实现的?这完全关乎为什么需要新的分析表以及这些关系是如何定义的。例如,支持用于社交媒体的移动应用所需的数据库表与用于销售分析的数据模型不相同。

在某些情况下,从数据生产者创建的数据模型可以用于消费者,这样你就不需要做任何修改,例如在前一章的例子中。在其他情况下,你只需通过添加新字段来扩展现有的数据模型,这些新字段可能来自新的数据源或从现有的数据行和列中派生出来。在任一情况下,从数据分析师的角度来看,我们通过考虑如何将列用作维度或度量来改变我们看待现有数据模型的方式。

数据模型中的维度是具有描述性属性的价值,通常用于识别一个人、地点或事物。记住维度字段的最简单方法是它们可以被归类为名词。一个很好的例子是日期维度,其中任何给定的日期值,例如12/03/1975,都会有多个可以从这个值派生出来的属性,如下表所示:

字段名称 字段值
日期 12/03/1975
年份 1975
月份编号 12
月份名称 December
星期 Wednesday
季度 Q4
是否周末标志 FALSE

数据模型中的度量(measure)是指任何可以采用统计计算(如summinmaxaverage)进行聚合的值。识别度量字段的简单方法是质疑这些值是否在行动中,这样您就可以将它们分类为动词。在许多情况下,度量值在表源中会以高频率重复。可以识别为度量的常见字段包括销售额、销售数量或收盘价。度量字段通常存储在可以抽象为fact表的表中。fact表可能已经由生产者定义,也可能在分析过程中派生。fact表代表数据库表中单行定义的事件、交易或实体。我们已在第五章中定义的主键(primary key)用于唯一标识每一行,以确保报告和分析的一致性。如果需要多个字段来唯一标识一条记录,则将创建一个代理键(surrogate key)字段。

连接到事实表(fact table)的将是一张或多张维度表,这些表以某种方式组织,能够以可重复的方式回答业务问题。与用于支持系统和应用的关联数据模型不同,维度数据模型应该为分析和报告而构建。

这些概念是在大约 10 年前由我的导师和前同事 Rich Hunter 在我俩在纽约州伯克利山区的 Axis Group, LLC 工作时介绍给我的。我会将在这家精品 BI 咨询公司工作的人定义为数据战士,因为他们能够理解、适应和利用数据解决问题。解决问题和创建数据解决方案是他们标准操作程序的一部分。正是在这段时间里,Rich 通过向我介绍一种新的看待数据的方式,改变了我对如何处理数据的看法,从而改变了我的职业生涯。我很高兴现在能与你们分享同样的基础数据分析概念。我将永远感激与我在 Axis Group 的前同事们一起工作。

这一切始于了解由 Ralph Kimball 和 Bill Inmon 创建的数据建模方法,他们通常被认为是数据仓库的奠基人。这两位男士定义了可以用于构建数据的途径,这些数据可以随着公司规模和主题领域的任何大小而扩展,重点是数据分析以及创建针对它的报告。他们对数据技术行业的贡献是如此巨大,以至于我将它们视为数据分析的演变的一部分,这在第一章中讨论的数据分析基础中有所提及。

数据仓库,通常称为企业数据仓库EDW),是从多个来源集中数据的位置,目的是提供、报告和分析数据。传统数据仓库按主题组织,如人力资源、销售或会计。根据数据复杂性(多样性体积速度),数据仓库可能位于一个技术解决方案的中心,或分布在不同技术中。

为什么一个组织会创建一个集中的数据仓库?因为针对数据源进行报告的一致性和准确性是值得成本的。在某些情况下,质量的重要性如此之高,以至于公司会投资创建一个或多个数据仓库。一旦组织确定了对所有数据的集中位置的需求,它通常会被认为是所有报告和分析的单一事实版本。理想情况下,这是不同技术来源的不同数据集可以被收集并符合定义和一致的业务规则的地方。销售经理和人力资源HR)经理可以分别查看来自不同报告的数据,但具有相同的准确性,以便做出数据驱动的决策。

现在你已经了解了为什么数据仓库是一个重要的概念,让我们简要回顾一下构建它们常用的两种方法。首先,它们不是针对特定技术定义的,因此当数据架构师创建它们时,重点是定义结构,这被称为数据库模式,以支持不同的主题领域。数据主题领域通常按业务线或组织结构组织。例如,人力资源仓库将专注于员工属性,如雇佣日期、经理和职位。一旦定义了表和关系,就可以在整个组织和其他下游系统(如员工福利和薪酬)中共享唯一的员工标识符。

我们用于在公司内部构建仓库设计的方法遵循两种不同的方法。比尔·英蒙建议自上而下的方法,即组织在实施前制定一个战略解决方案来定义一个共同的标准。拉尔夫·金伯尔推荐自下而上的方法,其中每个主题领域都是为支持管理决策需求而构建的,对严格标准的执行较少。

例如,我曾与一位客户一起工作,销售团队使用定义明确的地区进行地理层次报告,以组织他们开展业务的国家。因此,在仓库中定义了一个地区,使用精确的简写来表示值,并在任何报告或分析应用程序之间提供一致性。例如,EMEA代表欧洲、中东和非洲的所有国家。

这种方法一直有效,直到人力资源团队决定他们想要将 EMEA 地区的县分开,以便更好地进行报告和分析。为了解决这个问题,他们有几个选择,包括在国家和地区之间在层次结构中创建一个二级,这样报告就需要进行下钻;例如,从 EMEA 到中东到以色列。

另一个选择是创建一个独立于销售区域定义的人力资源区域字段。这使我们能够在不影响与销售分析相关的任何内容的情况下,向高管发送报告和分析细节。这个选项之所以有效,是因为工程团队能够取悦多个部门的商业用户,同时仍然在所有下游报告解决方案中创造一致性的价值。

为报告和分析对数据进行维度建模是技术无关的。虽然这个概念和技术通常在 SQL 解决方案中找到,但你并不局限于仅使用数据库技术来对分析中的数据进行建模。第一步是理解你试图用数据回答什么问题。然后,你可以决定哪种模型最能回答手头的问题。以下是一些可以应用于几乎任何数据模型的问题:

  • 谁? 在数据中识别“谁”应该是直截了当的。对于员工数据,谁是实际员工唯一标识符,以及构成该个人的所有属性,包括他们的名字、姓氏、出生日期、电子邮件、雇佣日期等。对于与销售相关的数据,客户就是“谁”,因此这将包括客户的姓名、邮寄地址、电子邮件等。

  • 什么? 正在购买什么产品或服务?这也会包括每个产品独特的所有属性(列)。所以,如果我购买了一杯咖啡,大小、口味和单位成本都应该包括在内。

  • 何时? 何时是一个非常常见的问题,与之相关,很容易识别出与回答这个问题相关的不同字段。所有日期、时间和时间戳字段都用于回答何时的问题。例如,如果网络用户访问了一个网站,那么“何时”就是记录该事件的特定日期和时间。如果日期/时间数据类型没有标准化或者没有考虑到时区,例如协调世界时UTC),这会变得更加复杂。一旦你有了所有相关值背后的具体细节粒度,就可以确定星期

  • 在哪里? 产品或服务的销售发生在哪里?我们是否有地理位置详情,或者它是一个有街道地址的商店?

  • 如何? 事件是如何发生的,它是如何发生的?活动的“如何”,事件或结果,比如用户点击按钮将项目添加到他们的购物车?

  • 为什么?我认为为什么通常是数据分析中最重要的一个问题,也是商业用户最常提出的问题。这个“为什么”是促销或营销活动,比如特定产品或服务的闪购吗?

如下所示图解可以直观地展示这一点,图中将所有这些关键问题作为维度,中心位置是一个事件事实。这通常被称为星型模式关系,其中高频率交易和大量行数据存储在事件事实中,而独特和唯一的值存储在解决什么何时何地如何为什么等问题的维度表中:

图片

一个经验法则是事件事实表较窄(具有少量字段和多个连接键字段)但行数多。维度表将具有独特的记录和宽属性(具有大量字段和单个连接键)。

在提问和回答这些问题的过程中,你最终会将源数据中的一个或多个字段组合在一起,形成定义好的关系。例如,对于所有客户属性,你可以创建一个包含所有不同字段、使用单一唯一键来识别每个客户的表格、CSV 文件或 DataFrame。创建数据模型是构建图表和数据可视化的基础。

图表和数据可视化最佳实践的解剖

那么,什么使一个好的图表或可视化?答案取决于几个因素,其中大多数都与你所处理的数据集有关。将数据集想象成一个具有每列或字段一致性的行和列的表格。例如,年份应该有201320142015等值,并且格式一致。如果你的数据集格式不一致或包含值混合,那么在创建图表之前建议清理数据集。数据清理或清洗是修复或从数据集中删除不准确记录的过程。图表需要统一性,例如按升序排序年份值以准确展示趋势。

让我们用一个简单的例子来说明,如下所示图解。在这里,数据集的左侧是一个具有四行、两列和标题行的统一表格。为了确保你理解这个概念,表格是一种图表,你可以定义维度和度量。这个图表的标题使我们能够轻松理解每行的值应该代表什么,因为每一列都有一致的格式。正如我们在第一章,“数据分析基础”中提到的,我们称之为数据类型,其中同一列中的每个值都将帮助创建一个图表,这将大大加快速度:

图片

对于前面的图表,在创建任何可视化之前,我们有一些明显的清理工作要做。这个图表中的年份列包含多种值,由于所有这些不一致性,这将使创建视觉趋势变得困难。此外,对于销售额(以百万为单位)列,由于值是数字和字符的混合,将很难创建任何聚合,例如总销售额的总和。无论使用什么工具创建图表,数据清理都将确保其质量和准确性。

分析您的数据

一旦我们有一个干净的数据集,我们就可以根据图表的基本原则——维度和度量来分类数据集的字段。正如我们之前讨论的,解释这种差异的最简单方法是,维度是一个名词,它被分类为人物、地点或事物。最常用的维度字段,可以应用于许多不同的数据集,是日期/时间,它允许您创建时间趋势。

度量是动词,它们是从您的数据中的动作列,允许聚合(总和、计数、平均值、最小值、最大值等)。在以下图表中,我们有一个标记为销售额(以百万为单位)的条形图,我已经确定了它们之间的区别:

图片

任何数据类型的字段都可以用作图表中的维度或度量,因此请确保您选择的视觉元素能够回答业务问题或提供所需的见解。

那么,为什么这些概念在创建图表时很重要?它们几乎适用于所有不同的图表类型,无论使用什么技术创建。显示时间趋势的条形图必须有一个日期维度(日、月或年)以及一些可以测量的内容——销售额、平均价格或用户数量。

现在,既然我们已经了解了基础知识,让我们通过几个例子来了解一下。以下图表所示的条形趋势图具有年份维度和按产品销售的销售额度量:

图片

为了增加多样性,图表具有多个维度。在这种情况下,我们有两个维度——年份和产品。年份在x轴上以一系列的形式显示,但没有标签。产品值以堆叠条形表示,每个值对应于y轴上图例测量的颜色。

图表可以以不同的类型可视化,包括最常见的:条形图、折线图和饼图。以下屏幕截图所示的折线图为我们提供了一个直观的方式来识别时间上的加速增长:

图片

在上一张截图中的组合图表在同一图表中同时包含线和柱状图,两个不同的度量值分别位于两个不同的轴上。在这个例子中,柱状图代表的是平均温度(华氏度)的度量值。这些值可以在右侧轴上看到,并使用华氏度作为刻度。线条显示的是平均降雨量(英寸)的度量值,该值在左侧轴上从 1.50 英寸到 6.00 英寸进行标注。

有两个度量值允许我们使用一个共同的维度(在先前的例子中是一个日期)对不同度量值进行比较分析。根据这个例子中使用的图表技术,不同的日期维度,如时间戳、日期或数值月份值,可以在x轴上以 MMM 格式表示的月份进行使用。这张图表的最终结果通过突出显示低和高异常值,而不需要在表中扫描或搜索它们,讲述了一个戏剧性的故事,说明了这两个度量值随时间如何比较。

为什么饼图失去了优势

饼图作为首选图表已经不再流行,但在过去被广泛使用。实际上,斯蒂芬·弗(Stephen Few)写的一整本书涵盖了仪表板的正确做法和错误做法,他指出为什么应该用替代品,如这里提供的水平柱状图来代替饼图。如果你想了解更多信息,我在进一步阅读部分提供了这本书的参考。

水平柱状图相对于饼图的一些关键优势如下:

  • 易于消费:我不需要查看图例来查找维度的值。

  • 排序能力:你可以按最高或最低值进行排序,以强调数据集中的重要性。

  • 两者兼得:它们具有分布和相对值,就像饼图一样。如果你看下面的图表,你会看到产品 1的面积是产品 5的两倍。在这里,你可以快速地看到差异,因为相同的x轴被用来方便地参考柱状图的宽度。

以下是一个水平柱状图的示例:

图片 1

如果你仍然需要使用饼图,它们在只有两个不同值时效果最佳。在以下例子中,你可以轻松地看到值之间的分布。使用主色强调,以及使用柔和的灰色颜色表示负值,有助于在这个图表中传达积极的消息:

图片 2

选择合适的图表是在作者想要用数据讲述的故事和从数据中可用的维度和度量之间取得平衡。我发现这是一种艺术和科学的方法,实践可以改进你创建良好可视化图表的能力。有关哪些图表能帮助你讲述最佳故事,有大量的资源可供参考。现在,你知道了任何使用维度和度量的图表类型的成分,你有一个可以在任何技术和主题领域应用的通用框架。选择合适的视觉图表来传达正确的信息也需要时间、经验和甚至尝试错误。

由于我将数据可视化归类为一种艺术和科学,因此花时间去了解你的业务问题确实需要时间。请随意使用进一步阅读部分中概述的图表选项作为指导。务必记住以下几点:

  • 重用代码是高度鼓励的:不要从头开始创建一个新的图表来重新发明轮子。找到一个好的例子,并尝试将你的数据拟合到维度和度量中。

  • 少即是多:避免干扰,让数据讲述故事——使用视觉提示来强调和突出异常值。过度使用多种颜色会分散消费者的注意力,因此只使用一种颜色来突出你想让图表观众关注的内容。

  • 外面有很多专家:利用他们!我在进一步阅读部分放置了一些 URL 链接以供参考。

艺术与科学

对我来说,数据可视化一种艺术和科学。对我来说,它始于四年级,我的老师塞格先生激发了我的创造力,并介绍了我认识了梵高、夏加尔和塞尚等大师工匠。艺术是通过视觉图像创造和激发自由思考。艺术如何定义是主观的,但它通常包括艺术元素,即形状、形式、价值、线条、颜色、空间和质感。让我们逐一分析:

  • 形状作为艺术元素,被定义为由边缘定义的区域。形状为艺术作品之外的消费者提供了视觉背景和边界。

  • 形式被定义为艺术作品的感知体积或维度。因此,形式将控制形状内的边界。

  • 价值是指在艺术作品中对亮度和暗度的运用。光显然是任何艺术作品的重要元素,包括全部的光谱和缺乏光的情况。

  • 线条作为艺术元素,可以是直线或曲线,跨越两点之间的距离,这使得艺术家能够在形式中定义强度。

  • 颜色可能是最著名的艺术元素,它来自于光线撞击物体的时候。这有助于艺术作品的消费者视觉上单独或整体地解读作品。颜色的特性包括色调,这是我们通常与红色黄色蓝色等颜色联系在一起的东西。颜色还包括强度,它由可用的完整光谱颜色控制,以及价值,它控制亮度。

  • 空间是艺术家在包含背景和前景时定义的区域。空间内或周围的距离是另一个需要考虑的重要因素。

  • 对于艺术品的消费者来说,纹理是二维艺术中描绘的视觉感觉。

科学关于经验证据,这可以定义为通过观察或实验获得的数据。数据一直是科学研究方法的一部分,对于收集证据以证明理论或假设至关重要。

当这两个概念结合在一起时,它们允许你通过提供洞察力和使我们能够立即理解趋势的数据可视化来用信息讲故事。

什么造就了出色的数据可视化?

对我来说,这是一个主观的问题,我的答案随着时间的推移而演变,这意味着提供直接答案可能是一个挑战。

我觉得美丽且直观的东西可能对其他人来说并不那么有洞察力。这类似于艺术作品,不同的风格、时期和艺术家会有喜爱者和批评者。在过去的几年里,我见证了数据可视化如何演变成为一种艺术形式,技术编码使得创造力得以蓬勃发展。从创建简单的图表、图形和图形的这种运动已经演变到通常所说的数据可视化

数据可视化可以是任何从信息图表到能够用数据讲述故事的动画视觉内容。当我看到一些美观且能激发我采取行动的东西时,我会将其归类为出色的数据可视化。这些行动可以根据提供的信息而有所不同,但常见的反应包括与他人分享数据可视化,目的是激发关于它的对话。此外,出色的数据可视化应该揭示模式和趋势。这有助于消费者轻松地将可操作信息与噪音区分开来。数据中的噪音意味着观众被图表所困惑,或者没有明显的视觉模式。展示的图形应该对观众来说直观易懂,不需要额外的上下文,也不应强迫消费者查找更多信息以理解图表。

我在第一章“数据分析基础”的“数据分析演变”部分中包含了一些数据可视化领域的专家。他们包括 Stephen Few、Edward Tufte 和 Alberto Cairo。我鼓励你研究他们关于这个主题的许多不同书籍和文章——我还在“进一步阅读”部分添加了一些他们的作品。

数据可视化领域的另一位冠军是 Naomi B Robinson,她是Meetup.com数据可视化纽约章节的创始人。这个公共社区汇集了来自任何行业的专业专家,他们拥有包括新闻、软件开发、数据架构师、UI/UX 专家、平面设计师和数据科学家在内的广泛专业技能。他们来自世界各地,他们的使命是分享最佳实践、专业知识,并创建一个开放的论坛来推广数据可视化作为一门技艺。会员对所有人均开放,所以我鼓励你作为会员加入,并希望你能像我一样享受这些活动。

这些年我在数据可视化方面获得的一些见解包括以下内容:

  • 数据可视化是一门技艺,需要时间来掌握,但作为职业将会有回报,因为你学习的技巧越多,你在创建它们时就会越擅长。

  • 单纯的技术并不能创造出伟大的数据可视化。技术使图表、仪表板或应用程序的作者能够创建数据解决方案,但在掌握每个工具时都有一定的学习曲线。

  • 一份优秀的可视化数据将激励人们推广和传播关于数据的故事,而无需了解它是如何创建的。

一份优秀的可视化数据示例可以在纪录片《难以忽视的真相》中找到,其中一条折线图是关注人们应该关注气候变化的原因的焦点。这个形状像曲棍球的单一图表的加入,引发了争议和讨论。更重要的是,这个数据可视化使用数据向科学界以外的观众讲述了一个故事。

比较分析

现在我们对图表的解剖结构有了更好的理解,我们可以通过解释图表中日期和时间趋势的一些差异来深入探讨时间序列图表。

解释日期和时间趋势

我们将从以下图表中显示的示例开始,其中我们有一个折线图,每个数据点都由一个单一值表示。可视化数据的第一个优点是,即使没有所有关于它是如何生成的背景信息,也可以很容易地解释结果:

图片

在前面的图表中强调的最佳实践是,显示的日期值采用标准的和一致的格式 YYYY-MM-DD。这有几个重要的原因。对于图表的制作者来说,保持一致的数据类型确保所有值在排序和完成时都是准确的,这意味着数据可视化与源数据相匹配。作为图表的制作者,还应考虑源数据中所有值中可用的最低日期值。这个概念被称为“数据粒度”,它决定了哪个日期可以用作图表的维度。如果同一字段中存在每日和月度数据值,你不应该以原样将它们显示在同一图表中,因为这可能会使消费者感到困惑。

我的同事迈克·乌尔西蒂喜欢说,好的仪表板设计是你创建了一个解决方案,消费者不需要思考,因此任何受众的解释都变得自然。当然,他说得对——很容易在日期值显示不一致或过度使用颜色引导的地方制造干扰,这会导致对图表制作者试图描绘的内容有更多疑问。因此,作为一名数据分析师,花时间思考哪种布局和设计将以逻辑方式呈现你的分析,并且易于任何受众消费。

在下面的图表中,我们与前面的例子具有相同的数据源,但现在数据已经按月汇总,表示为从 1 到 12 的整数。在这种情况下使用的标签很重要,可以帮助消费者理解用于表示每个时间段显示的值的聚合方式。从不同的日期格式查看数据的能力是学习数据分析的一个关键技能,并提高你的数据素养:

图片

要就图表中的见解进行争论,需要你理解是否正确地使用了适当的聚合。在许多情况下,这需要消费者和制作者都对数据主题及其使用方式有所了解。对于这个例子,按月汇总“每日收盘股价”的值对于这个数据域来说是不相关的,并且主题专家会称这种度量不准确。

然而,提供不同的日期格式为我们提供了一个机会,以可能最初未考虑过的方式查看数据。你还可以更快地提供见解,因为你不必在单个日期值中寻找特定的值。例如,查看 12 个月的趋势在折线图上显示 12 个数据点,而不是显示数十个甚至数百个单独的天数。

在图表中比较一段时间内的结果,可以快速发现异常值或识别数据中的趋势。例如,如果您绘制苹果公司每天收盘价的图表,您将根据线的倾斜度直观地看到上升或下降趋势。这允许消费者更快地识别数据中的模式,如果单独查看每个值,可能不那么明显。

另一个流行且有用的数据分析是逐年。如果您正在查看当前年份,这种分析通常被称为当年至今YTD)与去年至今PYTD)。在这种情况下,您必须定义当前年份的一个固定点,例如当前日期或月份,然后只包括与去年相对应的日期或月份。

下面的图表是这种有用分析的示例,我们比较了当前年份(被识别为 2019 年)1 月至 4 月每个月苹果公司股票交易的量,然后与去年(被识别为 2018 年)的同一个月份的结果进行比较。我发现使用折线图是可视化这种分析的有用方式,因为它很容易对比两条线的差异。一个简单的表格视图也会向数据的消费者展示它们之间的差异:

图表

下表显示了相同的数据,但以不同的图表类型显示。如何呈现这些数据的视觉偏好可能会有所不同,您应该能够为您的观众提供任一选项:

图表

曲线的形状

现在我们将深入探讨如何使用名为matplotlib的新库从数据创建可视化,这个库是在您第一次使用 Anaconda 时安装的。根据matplotlib.org的历史页面,这个库是从 MATLAB 图形演变而来的,由 John D. Hunter 创建,其理念是您应该能够仅用几个命令,或者一个命令就能创建简单的图表

就像我们介绍的大多数库一样,有许多功能和能力可供您创建图表和数据可视化。matplotlib库有一个生态系统,您可以根据不同的用例应用,很好地补充pandasnumpy库。

有许多教程和额外资源可供您学习这个库。我已经在进一步阅读部分添加了必要的链接供您参考。

在这个例子中,我们将加载一个包含上市公司在全国证券交易商自动报价交易所NASDAQ)的股票价格详情的 CSV 文件。我们将使用折线图来可视化一个维度和一个度量,看看我们是否能在数据中识别出任何趋势。

为了开始,让我们创建一个新的 Jupyter Notebook 并将其命名为 create_chart_with_matplotlib。在这个例子中,我们将使用我们在前几章中学到的几个库和命令。我会一步步地引导您通过代码,所以请随时跟随。我已经在 GitHub 上放置了一个副本供您参考。

创建您的第一个时间序列图表

按照以下步骤创建您的第一个时间序列图表:

  1. 要加载 pandas 库,请在您的 Jupyter Notebook 中使用以下命令并运行该单元格:
In[]: import pandas as pd

这个库应该已经通过 Anaconda 可用。有关设置环境的帮助,请参阅第二章Python 和 Jupyter Notebook 安装概述

  1. 接下来,我们可以将 CSV 文件加载到一个新的 DataFrame 中,命名为 df_stock_price,以便更容易识别。为了减少准备数据分析所需步骤的数量,我们向 pd.read_csv 函数传递一些参数命令来索引文件中的第一列。我们还将包括 parse_dates 参数来定义 Date 字段的类型为 datetime64
In[]: df_stock_price=pd.read_csv('AAPL.csv', index_col=0,parse_dates=True)

确保您已将 AAPL.csv 文件复制到正确的 Jupyter 文件夹目录中,以避免在导入数据文件时出错。

  1. 在可视化数据之前,作为一个良好的最佳实践,让我们确保 DataFrame 可以使用 head() 命令读取:
In[]: df_stock_price.head(3)
  1. 输出将如下所示,其中源 CSV 文件已被加载到 DataFrame 中,并带有标记的标题行,其中索引列被定义为 Date

  1. 为了验证索引字段的类型是否准确,您可以运行以下命令:
In[]: df_stock_price.index
  1. 输出将如下所示,其中为这个 DataFrame 定义的索引的 dtype 被分配给 Date 字段:

将索引定义为 datetime 数据类型字段系列将使绘图功能更容易处理。在可视化之前所做的更多数据准备将确保图表创建更快。

  1. 现在,我们可以导入 matplotlib 库,以便我们可以引用用于可视化数据的函数。Matplotlib 是一个功能强大的库,具有多个模块。在这个例子中,我们将明确关注 pyplot 模块。我们将使用常见的 plt 便捷方式来方便引用和采用最佳实践标准:
In[]: import matplotlib.pyplot as plt
  1. 让我们使用 plot() 函数快速创建一个线形图表,针对我们的 DataFrame。为了避免在解释可视化或处理图表时产生混淆,让我们包括特定的 Close 字段。在使用这个库时,确保在行尾包含一个分号:
In[]: df_stock_price['Close'].plot()

您可能需要在 Jupyter Notebook 中包含一行额外的 %matplotlib inline 来显示结果。这行额外的代码被称为魔法函数。

  1. 输出将如下所示,其中显示了一条折线图,这是默认选项。它使用 Date 字段作为 x 轴,这也是维度,使用 Close 价格字段值作为 y 轴,这是我们的度量:

图片

  1. plot() 函数有许多不同的参数可以用来自定义可视化。让我们通过调整以下命令中的线条宽度、颜色和线条样式来探索一些简单的更改:
In[]: df_stock_price['Close'].plot(linewidth=.5, color='green',linestyle='dotted')
  1. 输出将如下所示,其中显示了相同的折线图,但颜色已更改,线条宽度已减小,线条样式为虚线:

图片

有许多不同的参数允许您通过一些细微的调整来更改可视化的样式。我在“进一步阅读”部分为您提供了库的参考。

  1. 接下来,让我们通过添加标签来丰富图表功能,以便为消费者提供更多细节:
In[]: df_stock_price['Close'].plot(linewidth=.5, color='green',linestyle='dotted')
plt.xlabel('Close Date')
plt.ylabel('Close Price')
plt.suptitle('AAPL Stock Price Trend');
  1. 输出将如下所示,其中显示了之前的相同折线图,但现在我们包括了上下文细节,如维度和度量标签,以及图表的标题:

图片

  1. 该库中另一个可用的数据可视化功能是柱状图。要使用此功能,我们必须对命令进行一些调整。为了回答“股票量趋势是什么?”这个问题,我们可以使用这个命令。注意使用 .index 传递 Date 字段值:
In[]: plt.bar(df_stock_price.index,df_stock_price['Volume']);

输出将如下所示,其中图表以柱状图的形式显示值:

图片

  1. 然而,我注意到这个图表在向消费者展示时存在一些问题。标签重叠,因此难以理解可视化。让我们做一些小的调整,使图表更美观:
In[]: plt.bar(df_stock_price.index,df_stock_price['Volume'], color='blue')
plt.xticks(rotation = 90)
plt.yticks(fontsize = 10)
plt.xlabel('Date')
plt.ylabel('Volume')
plt.suptitle('AAPL Stock Volume Trend');

输出将如下所示,其中图表以柱状图的形式显示值。请注意,然而,标签已被添加,使图表更容易消费和理解:

图片

这些步骤定义了生成分析工作流程以可视化数据的最佳实践。一旦您将数据存储在 DataFrame 中并加载 matplotlib 库,可视化数据就会变得更快。在这个过程中调整参数时,进行一些尝试和错误是很常见的。始终保存您的作品和示例,这样您就可以轻松地重新创建提供快速洞察的数据可视化,而无需筛选数据行。

摘要

恭喜你——你现在已经学会了一些令人兴奋的新方法来可视化数据,并解释各种图表类型以帮助扩展你的数据素养技能!在本章中,你学习了一些最佳实践来找到适合特定分析类型的正确图表。你还学习了维度和度量之间的区别,以及如何建模数据以进行分析以回答问题。

接下来,你通过探索 pandas 中的各种时间序列和日期功能,学习了制作各种图表(如折线图和条形图)的必要技能。我们突出了数据可视化领域的领导者,如 Alberto Cairo 和 Naomi B. Robbins,并讨论了他们如何影响了数据分析的演变。最后,你使用了.plot()方法,通过matplotlib库创建时间序列图表。

在下一章中,我们将探讨我们可以使用的清理、精炼和合并多个数据集的技术。

进一步阅读

探索、清洗、精炼和融合数据集

在上一章中,我们学习了数据可视化的力量,以及拥有高质量、一致数据的重要性,这些数据通过维度和度量来定义。

现在我们已经理解了为什么这很重要,我们将通过实际操作数据,在本章中专注于如何。到目前为止,提供的多数示例都包括了已经准备(准备)好的数据,以便更容易消费。我们现在正在学习必要的技能,以便在处理数据时感到舒适,从而提高你的数据素养。

本章的一个关键概念是清洗、过滤和精炼数据。在许多情况下,你需要执行这些操作的原因是源数据本身并不提供高质量的分析。在我的整个职业生涯中,高质量的数据不是常态,数据缺口很常见。作为优秀的数据分析师,我们需要利用我们所能拥有的。我们将介绍一些技术来提高数据质量,这样你就可以在源数据不包含所需的所有信息的情况下,提供高质量的见解并回答数据中的问题。

根据我的经验,强调源数据质量差是一个洞察力,因为透明度不足,关键利益相关者对使用数据的挑战并不了解。底线是,数据质量差不应该阻止你继续与数据工作。我的目标是展示一个可重复的技术和工作流程,以提高分析数据的质量。

本章我们将涵盖以下主题:

  • 检索、查看和存储表格数据

  • 学习如何限制、排序和筛选数据

  • 使用 Python 清洗、精炼和净化数据

  • 合并和分箱数据

第十章:技术要求

这是本书的 GitHub 仓库链接:github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter07

你可以从以下链接下载和安装所需的软件:www.anaconda.com/products/individual

检索、查看和存储表格数据

在前面的章节中已经多次介绍了检索和查看表格数据的能力;然而,那些例子都是侧重于消费者的视角。我们学习了理解结构化数据是什么,它可以采取的许多不同形式,以及如何从数据中回答一些问题的技能。在这段时间里,我们的数据素养有所提高,但我们一直依赖数据源的生产者,通过使用一些 Python 命令或 SQL 命令使其更容易阅读。在本章中,我们将从仅作为消费者转变为现在成为数据的生产者,通过学习操纵数据以进行分析的技能。

作为一名优秀的数据分析师,您需要消费者和供应商技能谱系的两侧来解决更复杂的数据问题。例如,企业要求的一个常见度量标准,针对网站或移动用户,被称为使用分析。这意味着在时间快照中计算用户数量,例如按日、周、月和年。更重要的是,您想更好地了解这些用户是新的、回头的还是流失的。

与使用分析相关的一些常见问题如下:

  • 本日、本周或这个月有多少新用户访问了网站?

  • 本日、本周或这个月有多少回头用户访问了网站?

  • 本周、这个月或这一年我们失去了多少用户(60 天以上未活跃)?

要回答这些问题,您的数据源至少必须提供 timestamp 和唯一的 user_id 字段。在许多情况下,这些数据将具有高容量和速度,因此分析这些信息将需要正确的人员、流程和技术组合,我有幸与这些合作。数据工程团队构建数据摄取管道,以便使这些数据可用于报告和分析。

您可能需要与数据工程团队合作,将业务规则和汇总级别(也称为聚合)应用于包含回答用户分析问题所需额外字段的数据。在我们的示例中,我已经提供了一个更小的数据样本,我们将从现有的源数据文件中推导出新的数据字段。

我认为最好的学习方法是一起逐步操作,所以让我们创建一个新的 Jupyter 笔记本,命名为 user_churn_prep。我们将从数据库中检索数据并将其加载到 DataFrame 中开始,类似于第五章在 Python 中收集和加载数据中概述的步骤。为了简化,我们正在使用另一个 SQLite 数据库来检索源数据。

如果您想了解更多关于连接到 SQL 数据源的信息,请参阅第五章,在 Python 中收集和加载数据

检索

要创建连接并使用 SQLite,我们必须使用代码导入一个新的库。对于这个例子,我已经提供了一个名为 user_hits.db 的数据库文件,所以请确保您事先从我的 GitHub 仓库下载它:

  1. 要加载 SQLite 数据库连接,您只需在您的 Jupyter 笔记本中添加以下命令并运行该单元格。我已经在 GitHub 上放置了一个副本供参考:
In[]: import sqlite3
  1. 接下来,我们需要将一个连接分配给名为 conn 的变量,并指向数据库文件的位置,该文件名为 user_hits.db
In[]: conn = sqlite3.connect('user_hits.db')

确保您已将 user_hits.db 文件复制到正确的 Jupyter 文件夹目录中,以避免连接错误。

  1. 导入 pandas 库,以便您可以创建一个 DataFrame:
In[]: import pandas as pd
  1. 执行一个 SQL 语句并将结果分配给一个 DataFrame:
In[]: df_user_churn = pd.read_sql_query("SELECT * FROM tbl_user_hits;", conn)
  1. 现在我们已经将结果存储在 DataFrame 中,我们可以使用所有可用的 pandas 库命令来处理这些数据,而无需返回到数据库。您的代码应类似于以下截图:

图片

查看

执行以下步骤以查看检索到的数据的结果:

  1. 要查看结果,我们可以运行 head() 命令对此 DataFrame 进行操作,使用以下代码:
In[]: df_user_churn.head()

输出将类似于以下表格,其中 tbl_user_hits 表已被加载到 DataFrame 中,带有标签的标题行,左侧的索引列从 0 开始:

图片

在我们进行下一步之前,让我们使用一些元数据命令来验证我们加载的数据。

  1. 在下一个 In[]: 单元格中输入 df_user_churn.info() 并运行该单元格:
In[]: df_user_churn.info()

确认输出单元格显示 Out []。将显示多行,包括所有列的数据类型,类似于以下截图:

图片

存储

现在我们已经将数据作为 DataFrame 可用于 Jupyter 中的工作,让我们运行一些命令将其存储为文件以供参考。将数据作为分析快照存储是一种有用的技术,虽然我们的示例很简单,但这个概念将有助于未来的数据分析项目。

要将您的 DataFrame 存储到 CSV 文件中,您只需运行以下命令:

In[]: df_user_churn.to_csv('user_hits_export.csv')

结果将类似于以下截图,其中在您的当前 Jupyter 笔记本相同的项目文件夹中创建了一个新的 CSV 文件。根据您在工作站上使用的操作系统,结果可能会有所不同:

图片

您可以将 DataFrame 导出为其他格式,包括 Excel。您还应该注意您导出数据文件的文件路径。查看 进一步阅读 部分以获取更多信息。

学习如何限制、排序和筛选数据

现在我们已经将数据存储在 DataFrame 中,我们可以通过几个 Python 命令来了解如何限制、排序和筛选数据。我们将使用 pandas 来讲解的概念在 SQL 中也很常见,因此我也会包括相应的 SQL 命令以供参考。

限制

限制数据的概念,也称为过滤数据,主要是基于条件隔离一个或多个记录。简单的例子是当你只根据匹配特定字段和值检索结果时。例如,你可能只想看到某个用户或特定时间点的结果。限制数据的其他要求可能更复杂,包括需要复杂逻辑、业务规则和多个步骤的明确条件。我不会涵盖需要复杂逻辑的复杂示例,但将在 进一步阅读 部分添加一些参考。然而,涵盖的概念将教会你满足许多常见用例的基本技能。

在我们的第一个例子中,让我们从 DataFrame 中隔离一个特定的用户。使用 pandas 命令,这相当简单,所以让我们启动一个新的 Jupyter notebook,命名为 user_churn_restricting

  1. 导入 pandas 库,以便你可以创建一个 DataFrame:
In[]: import pandas as pd
  1. 通过从先前示例中创建的 CSV 文件加载数据创建一个新的 DataFrame:
In[]: df_user_churn = pd.read_csv('user_hits_export.csv');

文件路径和文件名必须与先前示例中使用的相同。

现在我们已经将所有用户数据加载到一个单独的 DataFrame 中,我们可以轻松地引用源数据集来限制结果。保留这个源 DataFrame 完整是一个最佳实践,这样你可以为其他目的和分析引用它。在分析过程中,根据不断变化的需求进行调整,或者你只能通过调整来获得洞察力,这也是常见的。

在我的职业生涯中,我遵循在处理数据时的一种常见做法,即 你不知道你不知道的,所以能够轻松地引用源数据而不撤销你的更改是很重要的。这通常被称为快照分析,并具有根据需要回滚更改的能力。

当处理大于十亿行的数据源时,快照将需要大量的资源,其中 RAM 和 CPU 将受到影响。你可能需要按日期增量快照,或者创建一个滚动的时间窗口来限制一次可以处理的数据量。

要将我们的数据限制到特定用户,我们将从源 DataFrame 创建一个新的 DataFrame。这样,如果我们需要调整创建新 DataFrame 所使用的过滤器,我们不需要从头开始重新运行所有步骤。

  1. 通过从源 DataFrame 加载数据创建一个新的 DataFrame。语法是嵌套的,所以你实际上是在同一个 df_user_churn DataFrame 内部调用它,并仅过滤出 userid 等于 1 的显式值:
In[]: df_user_restricted = df_user_churn[df_user_churn['userid']==1]
  1. 要查看和验证结果,你可以运行一个简单的 head() 命令:
In[]: df_user_restricted.head()

结果将类似于以下截图,其中新的 df_user_restricted DataFrame 中只有两行在 userid1 时有值:

截图

限制数据有助于隔离特定类型的分析记录,从而帮助回答更多的问题。在下一步中,我们可以开始回答与使用模式相关的问题。

排序

现在我们已经通过创建一个新的 DataFrame 来隔离了特定的用户,该 DataFrame 现在可供参考,我们可以通过提出以下问题来增强我们的分析:

  • 特定用户是什么时候开始使用我们的网站的?

  • 这位用户多久访问一次我们的网站?

  • 这位用户最后一次访问我们的网站是什么时候?

所有这些问题都可以通过一些简单的 Python 命令来回答,这些命令专注于排序命令。排序数据是任何编程语言的计算机程序员都熟悉的一项技能。通过添加order by命令,很容易用 SQL 完成排序。排序数据的概念是众所周知的,因此我将不会深入定义;相反,我将专注于在执行数据分析时的重要功能和最佳实践。

在结构化数据中,排序通常被理解为按特定列的行级排序,这将通过从低到高或从高到低排序值的顺序来定义。默认情况下是低到高,除非你明确更改它。如果值的数据类型是数值型,例如整数或浮点数,排序顺序将很容易识别。对于文本数据,值将按字母顺序排序,并且根据所使用的技术的不同,混合大小写的文本将被不同地处理。在 Python 和 pandas 中,我们有特定的函数和参数,可以处理许多不同的用例和需求。

让我们开始使用sort()函数回答我们之前概述的一些问题:

  1. 要回答问题“特定用户是什么时候开始使用我们的网站的?”,我们只需要运行以下命令:
In[]: df_user_restricted.sort_values(by='date')

结果将类似于以下截图,其中结果按date字段升序排序:

图片

  1. 要回答问题“这位用户最后一次访问我们的网站是什么时候?”,我们只需要运行以下命令:
In[]: df_user_restricted.sort_values(by='date', ascending=False)

结果将类似于以下截图,其中显示的记录与之前的相同;然而,值是按此特定userid的最后可用日期降序排序的:

图片

筛选

数据筛选的概念意味着我们根据一个或多个条件从数据集中隔离特定的列和/或行。在筛选与限制之间有一些细微的差别,所以我将筛选定义为需要对数据集的群体应用额外的业务规则或条件,以隔离数据的一个子集。筛选数据通常需要从源数据中创建新的派生列,以回答更复杂的问题。对于我们的下一个例子,关于使用情况的一个好问题是:在周一访问我们网站的同一位用户是否也在同一周内返回?

为了回答这个问题,我们需要隔离特定星期的使用模式。这个过程需要几个步骤,我们将从之前创建的原始 DataFrame 中一起概述:

  1. 通过从源文件加载数据创建一个新的 DataFrame:
In[]: df_user_churn_cleaned = pd.read_csv('user_hits_binning_import.csv', parse_dates=['date'])

接下来,我们需要通过添加新的派生列来扩展 DataFrame,以帮助简化分析。由于我们有可用的 Timestamp 字段,pandas 库有一些非常有用的函数可以帮助这个过程。标准 SQL 也有内置的功能,并且会根据使用的 RDMS 而有所不同,因此您需要参考可用的日期/时间函数。例如,Postgres 数据库使用 select to_char(current_date,'Day'); 语法将日期字段转换为当前星期几。

  1. 导入一个新的 datetime 库,以便轻松引用日期和时间函数:
In[]: import datetime
  1. 为当前的 datetime 分配一个变量,以便更容易地计算从今天起的 age
In[]: now = pd.to_datetime('now')
  1. 添加一个名为 age 的新派生列,该列是从当前日期减去每个用户的日期值计算得出的:
In[]: df_user_churn_cleaned['age'] = now - df_user_churn_cleaned['date']

如果您在笔记本中收到 datetime 错误,您可能需要升级您的 pandas 库。

使用 Python 清理、精炼和净化数据

数据质量对于任何数据分析和分析都至关重要。在许多情况下,您直到开始处理数据之前都不会了解数据质量是好是坏。我会将高质量数据定义为结构良好、定义明确且一致的信息,其中每个字段中的几乎所有值都按照预期定义。根据我的经验,数据仓库将拥有高质量的数据,因为整个组织都有报告。根据我的经验,不良的数据质量出现在数据源缺乏透明度的地方。不良的数据质量示例包括预期数据类型的不一致性和分隔数据集中值的一致模式。为了帮助解决这些数据质量问题,您可以从我们在第一章,“数据分析基础”,中涵盖的概念和问题开始理解您的数据,即了解您的数据 (KYD)。由于数据质量会因来源而异,您可以提出以下一些具体问题来了解数据质量:

  • 数据是有结构还是无结构的?

  • 数据的来源是否可以追溯到某个系统或应用程序?

  • 数据是否被转换并存储在仓库中?

  • 数据是否有具有定义的数据类型的模式?

  • 您是否有包含业务规则文档的数据字典可用?

在事先收到这些问题的答案将是一种奢侈;在过程中发现它们对于数据分析师来说更为常见。在这个过程中,你仍然需要清洗、精炼和纯化你的数据以供分析。你需要花费多少时间将取决于许多不同的因素,而真正质量成本将是提高数据质量所需的时间和精力。

数据清洗可以采取多种不同的形式,并且几十年来一直是数据工程师和分析实践者的常见做法。企业级和大数据清洗需要许多不同的技术和技能集。数据清洗是信息技术(IT)行业的一部分,因为高质量数据值得外包的价格。

数据清洗的常见定义是从源数据中移除或解决低质量数据记录的过程,这取决于用于持久化数据的技术的不同,例如数据库表或编码文件。低质量数据可以识别为任何不符合生产者预期和定义要求的数据。这可以包括以下内容:

  • 一行或多行字段中的缺失或空(NaN)值

  • 孤立记录,其中主键或外键在任何引用的源表中找不到

  • 腐坏记录,其中一条或多条记录无法被任何报告或分析技术读取

以我们的示例为例,让我们再次查看我们的使用数据,并看看我们是否可以通过分析它来发现任何问题,以查看是否存在任何异常:

  1. 导入 CSV 文件并运行info()命令以确认数据类型和行数,并获取更多关于 DataFrame 的信息的概要:
In[]: df_usage_patterns = pd.read_csv('user_hits_import.csv')
df_usage_patterns.info()

结果将类似于以下截图,其中展示了 DataFrame 的元数据:

图片

发现的一个异常是两个字段之间的值数量不同。对于userid字段,有 9 个非空值,而对于date字段,有 12 个非空值。对于这个数据集,我们期望每一行两个字段都有一个值,但这个命令告诉我们存在缺失值。让我们运行另一个命令来识别哪个索引/行有缺失数据。

  1. 运行isnull()命令以确认数据类型和行数,并获取更多关于 DataFrame 的信息的概要:
In[]: pd.isnull(df_usage_patterns)

结果将类似于以下表格,其中按行和列显示了一列TrueFalse值:

图片

记录计数看起来不错,但请注意userid字段中存在空值(NaN)。对于每行数据的唯一标识符对于准确分析这些数据至关重要。userid为空的原因需要由数据的生产者解释,可能需要额外的工程资源来帮助调查和排除问题的根本原因。在某些情况下,这可能是数据源创建过程中简单的技术故障,需要微小的代码更改和重新处理。

我总是建议尽可能接近数据源进行数据清理,这样可以节省时间,避免其他数据分析师或报告系统重新工作。

在我们的分析中包含空值将影响我们的汇总统计和指标。例如,平均每日用户的计数在存在空值的日期会较低。对于用户流失分析,报告用户的频率度量将受到扭曲,因为 NaN 值可能是返回的user_ids之一或新用户。

对于任何高容量基于事务的系统,可能存在需要考虑的误差范围。作为一名优秀的数据分析师,要问的问题是:“质量成本和百分之一百准确性的成本是多少?”如果由于更改所需的时间和资源而价格过高,一个好的替代方案是排除和隔离缺失数据,以便稍后进行调查。

注意,如果您最终将隔离的数据添加回分析中,您将不得不重新陈述结果,并通知任何消费者您的指标发生了变化。

让我们通过一个例子来了解如何通过识别 NaN 记录并创建一个新的 DataFrame 来隔离和排除任何缺失数据:

  1. 通过从源 DataFrame 加载数据创建一个新的 DataFrame,但我们将通过添加dropna()命令排除空值:
In[]: df_user_churn_cleaned = df_usage_patterns.dropna()
  1. 要查看和验证结果,您可以运行一个简单的head()命令,并确认 NaN/空值已被删除:
In[]: df_user_churn_cleaned.head(10)

结果将类似于以下表格,其中新的 DataFrame 包含没有在useriddate中缺失值的完整记录:

图片

数据合并和分箱

由于多种原因,有时需要合并多个数据源,以下是一些原因:

  • 源数据被拆分为许多具有相同定义模式(表和字段名称)的不同文件,但行数会有所不同。一个常见的原因是为了存储目的,与一个大型文件相比,维护多个较小的文件大小更容易。

  • 数据被分区,其中一个字段用于将数据拆分以加快对源数据的读取或写入响应时间。例如,HIVE/HDFS 建议按单个日期值存储数据,这样您可以轻松地识别数据何时被处理,并快速提取特定一天的数据。

  • 历史数据存储在不同于当前数据的技术中。例如,工程团队更改了用于管理源数据的技术,并决定不导入特定日期之后的历史数据。

由于这里定义的任何原因,数据合并是数据分析中的常见做法。我将数据合并的过程定义为当你将两个或多个数据源分层到一个地方,其中所有来源的相同字段/列对齐时。在 SQL 中,这被称为 UNION ALL,而在 pandas 中,我们使用 concat() 函数将所有数据汇集在一起。

数据合并的一个良好视觉示例如下截图所示,其中多个源文件命名为 user_data_YYYY.cvs,并且每年都定义为 YYYY。这三个文件,它们都具有相同的字段名 useriddateyear,被导入到一个名为 tbl_user_data_stage 的 SQL 表中,如下截图所示。存储此信息的目标表还包括一个名为 filesource 的新字段,以便数据来源对生产者和消费者都更加透明:

截图

一旦数据被处理并持久化到名为 tbl_user_data_stage 的表中,所有三个文件中的记录都将按以下表格所示保留。在此示例中,任何重复的记录都将保留在源文件和目标表之间:

截图

数据工程团队创建 阶段 表的一个原因是为了帮助构建数据摄取管道并创建业务规则,其中重复的记录被删除。

为了在 Jupyter 中重现示例,让我们创建一个新的笔记本并将其命名为 ch_07_combining_data。导入多个文件有更高效的方法,但在我们的示例中,我们将分别将每个文件导入到单独的 DataFrame 中,然后合并它们:

  1. 导入 pandas 库:
In[]: import pandas as pd

您还需要将三个 CSV 文件复制到您的本地文件夹中。

  1. 导入名为 user_data_2017.csv 的第一个 CSV 文件:
In[]: df_user_data_2017 = pd.read_csv('user_data_2017.csv')
  1. 运行 head() 命令以验证结果:
In[]: df_user_data_2017.head()

结果将类似于以下截图,其中行以标题行和从值 0 开始的索引显示:

截图

  1. 对下一个 CSV 文件重复此过程,该文件名为 user_data_2018.csv
In[]: df_user_data_2018 = pd.read_csv('user_data_2018.csv')
  1. 运行 head() 命令以验证结果:
In[]: df_user_data_2018.head()

结果将类似于以下截图,其中行以标题行和从值 0 开始的索引显示:

截图

  1. 对下一个 CSV 文件重复此过程,该文件名为 user_data_2019.csv
In[]: df_user_data_2019 = pd.read_csv('user_data_2019.csv')
  1. 运行 head() 命令以验证结果:
In[]: df_user_data_2019.head()

结果将类似于以下截图,其中行以标题行和从值 0 开始的索引显示:

图片

  1. 下一步是使用concat()函数合并 DataFrame。我们包含ignore_index=True参数以为所有结果创建一个新的索引值:
In[]: df_user_data_combined = pd.concat([df_user_data_2017, df_user_data_2018, df_user_data_2019], ignore_index=True)
  1. 运行head()命令以验证结果:
In[]: df_user_data_combined.head(10)

结果将类似于以下截图,其中行以标题行和从值0开始的索引显示:

图片

分箱

分箱是一种非常常见的分析技术,允许您根据一个或多个标准对数值数据进行分组。这些组成为命名类别;它们在本质上是有序的,并且可以在范围之间具有相等的宽度或自定义要求。一个很好的例子是年龄范围,您通常在以下截图中所见的调查中看到:

图片

在这个例子中,一个人的年龄范围是输入,但如果我们实际上在数据中有了每个人的出生日期怎么办?那么,我们可以计算今天的年龄并根据前面的截图中的标准分配年龄段。这就是分箱数据的过程。

另一个常见的例子是天气数据,其中温暖等分配给华氏或摄氏温度的范围。每个区间值由数据分析师任意决定的条件定义。

对于我们的用户数据,让我们根据用户首次出现在我们的数据集中时的时间来分配年龄区间。我们将根据要求定义三个区间,这使我们能够调整分配的范围。在我们的例子中,我们定义区间如下:

  • 不到 1 年

  • 1 至 2 年

  • 超过 3 年

如何创建区间的具体条件,一旦我们通过代码演示,就会变得明显。

这种类型分析的另一项酷特性是,我们的计算年龄是基于使用数据和每次我们运行代码时计算的一个时间点。例如,如果用户第一次访问网站的时间是2017 年 1 月 1 日,而我们在这个分析中是在 2018 年 12 月 3 日进行的,那么用户的年龄(以天为单位)将是 360 天,这将分配给不到 1 年的区间。

如果我们在稍后的日期,例如 2019 年 11 月 18 日,重新运行此分析,计算出的年龄将改变,因此新的分配区间将是1 至 2 年

每个区间添加逻辑的位置的决定会有所不同。最灵活地更改分配的区间是在您提供分析的地方添加逻辑。在我们的例子中,那将是直接在 Jupyter 笔记本中。然而,在企业环境中,可能使用许多不同的技术来提供相同分析,将分箱逻辑移得更靠近源是有意义的。在某些情况下,存储在数据库中的非常大的数据集,使用 SQL 甚至更改表的模式是一个更好的选择。

如果你像我一样有幸拥有一个熟练的数据工程团队和与大数据工作的经验,将分箱逻辑移至数据表附近的决定就很容易了。在 SQL 中,你可以使用CASE 语句或 if/then 逻辑。Qlik 有一个名为class()的函数,可以根据线性尺度对值进行分箱。在 Microsoft Excel 中,可以使用嵌套公式根据函数混合来分配分箱。

因此,分箱的概念可以应用于不同的技术,作为一名优秀的数据分析师,你现在已经具备了理解如何实现它的基础。

让我们通过使用我们的使用数据和 Jupyter Notebook 的示例来巩固知识。

在执行以下步骤之前,请记住将任何依赖的 CSV 文件复制到工作文件夹中。

为了在 Jupyter 中重现示例,让我们创建一个新的笔记本,并将其命名为ch_07_sifting_and_binning_data

  1. 导入pandas库:
In[]: import pandas as pd
  1. 读取提供的 CSV 文件,该文件包含本例的附加数据,并创建一个新的 DataFrame,命名为df_user_churn_cleaned。我们还在导入时使用parse_dates参数将源 CSV 文件中找到的date字段转换为datetime64数据类型,这将使在接下来的几个步骤中更容易操作:
In[]: df_user_churn_cleaned = pd.read_csv('user_hits_binning_import.csv', parse_dates=['date'])
  1. 使用head()函数验证 DataFrame 是否有效:
In[]: df_user_churn_cleaned.head(10)

函数的输出将类似于以下表格,其中 DataFrame 已加载具有正确数据类型的两个字段,并可用于分析:

  1. 导入datetimenumpy库以供稍后参考,用于计算userid字段的age值:
In[]: from datetime import datetime
      import numpy as np
  1. 通过使用now函数和date字段计算当前日期和时间之间的差异来创建一个新的派生列age。为了以天为单位格式化age字段,我们包括dt.days函数,它将值转换为干净的"%d"格式:
In[]: #df_user_churn_cleaned['age'] = (datetime.now() - pd.to_datetime(df_user_churn_cleaned['date'])).dt.days
df_user_churn_cleaned['age'] = (datetime(2020, 2, 28)  - pd.to_datetime(df_user_churn_cleaned['date'])).dt.days

为了与截图匹配,我明确地将日期值定义为 2020-02-28,日期格式为 YYYY-MM-DD。你可以取消注释前面的行来计算当前的时间戳。由于时间戳每次运行函数都会变化,结果将不会与任何图像完全匹配。

  1. 验证新的age列是否已包含在你的 DataFrame 中:
In[]: df_user_churn_cleaned.head()

函数的输出将类似于以下表格,其中 DataFrame 已经从原始导入中修改,并包含一个名为age的新字段:

  1. 创建一个新的 DataFrame,命名为df_ages,它从现有的 DataFrame 中分组维度并按userid计算最大age值:
In[]: df_ages = df_user_churn_cleaned.groupby('userid').max()

输出将类似于以下截图,其中行数已从源 DataFrame 中减少。仅显示具有最大age值的唯一userid值,当第一条记录由userid创建时:

  1. 通过使用 pandas 库的 cut() 函数创建一个新的 age_bin 列。这将把 age 字段中的每个值放置在我们分配的 bins 范围之一之间。我们使用 labels 参数使分析对任何受众都更容易理解。注意,我们选择了 9999 的值来为 age 值创建一个最大边界:
In[]: df_ages['age_bin'] = pd.cut(x=df_ages['age'], bins=[1, 365, 730, 9999], labels=['< 1 year', '1 to 2 years', '> 3 years'])

  1. 显示 DataFrame 并验证显示的 bin 值:
In[]: df_ages

函数的输出将类似于以下截图,其中 DataFrame 已被修改,我们现在可以看到 age_bin 字段中的值:

图片

摘要

恭喜你,通过作为数据分析的消费者和生产者来处理数据,你已经提高了你的数据素养技能。我们涵盖了一些重要主题,包括通过创建数据视图、排序和从 SQL 源查询表格数据来操纵数据的必要技能。你现在有一个可重复的工作流程,可以将多个数据源合并成一个精炼的数据集。

我们探讨了使用 pandas DataFrame 的其他功能,展示了如何限制和筛选数据。我们通过使用 u用户流失 的概念来介绍现实世界的实际示例,以回答关于使用模式的关键业务问题,通过隔离特定用户和处理源数据中的缺失值。

我们下一章是第八章,理解连接、关系和数据聚合。除了使用聚合的概念创建总结分析外,我们还将详细介绍如何通过定义的关系连接数据。

进一步阅读

你可以参考以下链接以获取有关本章主题的更多信息:

理解连接、关系和聚合

我对这一章非常兴奋,因为我们将要学习关于合并多个数据集基础的知识。这个概念已经存在了几十年,使用 SQL 和其他技术,包括 R、pandas、Excel、Cognos 和 Qlikview。

合并数据的能力是一项强大的技能,它适用于不同的技术,并帮助你回答复杂的问题,例如产品销售如何受到天气预报的影响。数据源是互斥的,但今天,通过基于地理位置和时间的一些连接,可以轻松地将天气数据添加到你的数据模型中。我们将介绍如何做到这一点,以及不同类型的连接。一旦接触到这个概念,你将了解根据数据的粒度可以回答哪些问题。在我们的天气和销售数据示例中,细节变得非常重要,以便了解可以进行的分析水平。如果你想知道雨是否会影响销售,那么在合并数据后,你必须确保日期、星期和时间的更常见字段以及经纬度这样的地理位置标签在两个数据源中都可用,以便在得出结论后保持准确性。

在本章中,我们将学习如何构建高质量的数据集以进行进一步分析。我们将通过学习如何处理连接关系以及如何创建用于分析的数据聚合来进一步提高你的动手数据素养技能。

在本章中,我们将涵盖以下主题:

  • 连接关系的基础

  • 连接类型的应用

  • 解释数据聚合

  • 概述统计和异常值

第十一章:技术要求

这是本书的 GitHub 仓库链接:github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter08

你可以从以下链接下载和安装所需的软件:www.anaconda.com/products/individual

连接关系的基础

对于熟悉 SQL 的人来说,将数据合并在一起的概念已经非常清楚。将一个或多个表合并在一起进行数据分析的能力在我的 20 多年数据工作生涯中一直保持相关性,并且我希望它将继续保持相关性。

在前面的章节中,我们介绍了数据模型的概念以及定义关系时需要主键和外键字段的需求。现在,我们将通过解释连接以及 SQL 和 DataFrames 中存在的不同类型的连接来详细阐述这些概念。

在 SQL 中,连接简单地意味着将两个或多个表合并成一个单一的数据集。这个单一数据集的大小和形状将取决于所使用的连接类型。当你创建数据集之间的连接时,你想要记住的一些关键概念是应该始终使用公共唯一键。理想情况下,键字段同时作为主键和外键,但可以使用多个字段来定义所有数据行的唯一记录。

在本节中,我们将介绍以下类型的连接关系:

  • 一对一

  • 多对多

  • 左连接

  • 右连接

  • 内连接

  • 外连接

在我们的示例中,pandas DataFrame 的索引是默认的或单个定义的字段,因为它们具有唯一的值,但这并不总是如此。

一对一关系

一对一关系意味着源数据按行具有共同的唯一值,且不存在重复。在 Excel 中,具有启用精确匹配参数的vlookup函数具有类似的功能。当你使用这个 Excel 函数时,任何与源标识符匹配的都会返回一个与目标查找不同的值。在 SQL 中,一对一关系确保两个表之间的完整性保持一致。需要这些类型关系的原因有很多,但一个好的例子是将具有销售区域的参考表与唯一的客户表连接起来。在这个例子中,客户标识符(ID)字段将存在于两个表中,并且你永远不会在没有客户记录的情况下有销售区域,反之亦然。

多对一关系

多对一关系意味着其中一个源可以具有重复的行,但不是两个源都可以。你仍然需要在源之间有一个唯一键、索引或标识符。一个例子是将我们已在第七章,探索数据集的清理、精炼和混合中讨论过的查找维度表与事实表连接起来。

一个事务事实表将会有重复的记录,因为用户访问网站或产品销售的事件会按日期/时间记录,每个事件都会生成。结果将在userid字段中产生数百万行具有重复记录的行。当连接使用事实表和第二表之间的公共字段,如userid时,第二表中的每一行都必须是唯一的。这个第二表将包含关于userid的附加属性,例如城市邮编,这将提供更丰富的分析选项。一旦你理解了两个源表之间存在这种类型的连接关系,你就可以自信地将它们连接起来。

多对多关系

多对多关系是指两个来源都有重复的行。同样,您应该在来源之间找到一个共同的唯一键(一个或多个字段)或索引。这些类型的连接通常被称为昂贵的连接,因为所需的计算资源(内存和 CPU)将根据来源中的记录和列的数量显著增加。一个常见的例子是学生和班级之间的逻辑关系,其中许多学生可以参加许多不同的课程。相反,许多班级可以有不同数量的学生。作为一个最佳实践,我会尽量避免直接的多对多连接,并使用桥表来解决它们以进行分析。对于学生到班级的例子,您需要一个名单表,将每个班级中每个学生的唯一列表配对,如下面的截图所示:

截图

对于任何连接,你应该避免笛卡尔积,这是由于连接而产生的所有可能的行和列的组合。在某些情况下,这可能在您的分析中很有用,但请谨慎,尤其是在处理大量大数据时。

笛卡尔积是指所有可能的行和列的组合。在我们的例子中,如果我们不包含 tbl_roster 而将 tbl_studentstbl_classes 连接起来,您最终会得到被分配到他们从未注册的课程中的学生。有时我会故意构建一个笛卡尔积,因为它是进行某些类型分析或图表所需的。例如,如果您有一个从 1 到 10 的学生排名量表,但没有学生达到所有可能的值,您可以通过创建笛卡尔连接来填补缺失值的空白。

与处理多对多连接类型时对超出内存和 CPU 利用率的担忧类似,笛卡尔积可以轻易消耗所有可用内存,这可能导致您的工作站或工作空间崩溃。

左连接

现在我们已经涵盖了关键概念,我将开始用一个分析中最常见的连接类型之一的视觉表示来开始,这被称为左连接。让我们首先看看这个例子的源数据,如下面的截图所示:

截图

如您所见,我们有两个表名为 tbl_user_hitstbl_user_geotbl_user_hits 的宽度是两列,长度是三行。在 tbl_user_geo 表中,它代表用户的地理位置,我们有三列和五行。这两个表都有一个名为 userid 的公共字段,我已经将其突出显示,并会使用它来连接数据。这些表之间存在主键和外键的多对一关系,因为一个表中的所有记录并不都存在于另一个表中。

对于这个例子,我们希望保留tbl_user_hits表中的所有记录,并通过合并匹配的属性(如城市和州)来丰富数据,其中userid仅存在于用户点击表中的记录。结果如下面的截图所示,原始来源tbl_user_hits具有相同的行数,但现在包括了来自tbl_user_geo表的列:

图片

为什么行数保持不变但列数增加?成功的左连接保留源中的行数并扩展列数。

可以定义包含在连接结果中的特定列,但默认情况下将包括所有列。

为什么左连接在分析中很常见?那是因为我们感兴趣的是添加更多的维度字段来回答更多关于数据的问题,而这些数据仅存在于单个表中。此外,在你的分析中,你通常不想包含任何与我们的源用户点击不匹配的内容。有了这个新的连接结果,我们现在可以回答诸如哪个城市有最多用户等问题。通过一些日期计算,我们还可以按州提供月度趋势。

右连接

接下来,我们有一个称为右连接的连接类型,我认为它不太常见。那是因为没有多少用例需要创建合并数据记录中的空白。右连接是你想要保留第二个表的所有列和行,并用第一个表中的匹配值填充。

为了理解这个概念,首先回顾一下我们之前的源表tbl_user_hitstbl_user_geo。成功的右连接结果如下面的截图所示,其中连接结果显示了五行和四列。tbl_user_hits的日期字段已与tbl_user_geo源合并,但缺失的值将显示为null()NaN。请注意,如果tbl_user_hits有数千或数百万行,连接结果将增加tbl_user_geo的原始大小:

图片

使用右连接的一个优点是现在你可以识别哪些城市和州没有任何用户点击。这可能是有用的信息,可用于营销活动。

内连接

接下来,我们有一个内连接。这是当两个表都返回精确值以及所有列时。为了演示效果,我对我们的源表做了一些调整,如下面的截图所示。表名相同,但现在tbl_user_geo中删除了一些先前记录。这可能是因为法规要求,或者userid行可能被确定为无效,因此现在我们可以使用内连接将它们从tbl_user_hits中删除:

图片

连接结果在以下屏幕截图中显示,其中仅显示在userid键字段中找到的匹配值,以及两个源表之间的所有合并列:

图片

执行连接的 pandas 函数称为merge,内连接是默认选项。

外连接

最后,我们有一个外连接,它提供了来自两个源的所有行和列的完整列表。与笛卡尔积不同,笛卡尔积会创建任何可能的值组合,外连接反映了两个源表的真实情况。在我们的示例中,我们将使用以下屏幕截图中的相同源,其中已删除tbl_user_geo记录。与内连接不同,外连接结果允许你在 SQL 中看到任何缺失的记录作为 null,或在 Python/pandas 中作为NaN

图片

虽然这些概念和常见的连接类型并不全面,但你现在对将数据源连接在一起有了很好的基础理解,因此我们可以继续查看实际示例。

动态连接类型

不幸的是,我们使用的 SQLite 数据库不支持所有连接选项(右连接和外连接),因此我将只提供两个使用 SQL 的连接示例(左连接和内连接)。好消息是,pandas库使用merge()函数支持所有连接类型,因此我们可以重新创建所有已讨论的示例。请随意查看以下代码;我已经在 GitHub 上放置了 Jupyter Notebook 代码的副本以供参考。

在查看所有步骤之前,请确保将任何依赖文件复制到你的工作文件夹中。

我们将首先启动一个新的 Jupyter 笔记本,并将其命名为ch_08_exercises

  1. 加载 SQLite 数据库连接:
In[]: import sqlite3

这个库应该已经通过 Anaconda 可用。有关设置环境的帮助,请参阅第二章,Python 和 Jupyter Notebook 的概述

  1. 接下来,我们需要将一个名为conn的连接分配给变量,并指向名为user_hits.db的数据库文件位置。由于我们在先前的In[]行中已经导入了sqlite3库,我们可以使用这个内置函数与数据库通信:
In[]: conn = sqlite3.connect('user_hits.db')

确保你已经将user_hits.db文件复制到正确的 Jupyter 文件夹目录中,以避免连接错误。

  1. 导入pandas库:
In[]: import pandas as pd
  1. 要运行 SQL 语句并将结果分配给 DataFrame,我们必须运行这一行代码。pandas库包含一个read_sql_query函数,这使得使用 SQL 与数据库通信变得更容易。它需要一个连接参数,我们在前面的步骤中将其命名为conn。我们将结果分配给一个新的 DataFrame 作为df_left_join,以便更容易识别:
In[]: df_left_join = pd.read_sql_query("select u.userid, u.date, g.city, g.state from tbl_user_hits u left join tbl_user_geo g on u.userid = g.userid;", conn)

SQL 支持表名的别名概念,因此您可以缩短语法。在这个例子中,tbl_user_hits 的别名为 utbl_user_geog。这有助于在显式调用需要表名前缀的字段名时。

  1. 现在我们已经将结果放入 DataFrame 中,我们可以使用所有可用的 pandas 库命令对此数据进行操作,而无需返回数据库。要查看结果,我们只需运行 head() 命令对此 DataFrame 使用此代码:
In[]: df_left_join.head()

输出将类似于以下截图,其中 SQL 结果已加载到具有标记标题行的 DataFrame 中,索引列位于左侧,起始值为 0

截图

  1. 下一个连接将是一个内部连接,我们将结果分配给一个新的 DataFrame,命名为 df_inner_join,以便更容易识别:
df_inner_join = pd.read_sql_query("select u.userid, u.date, g.city, g.state from tbl_user_hits u, tbl_user_geo g where u.userid = g.userid;", conn)
  1. 现在我们已经将结果放入 DataFrame 中,我们可以使用所有可用的 pandas 库命令对此数据进行操作,而无需返回数据库。要查看结果,我们只需运行 head() 命令对此 DataFrame 使用此代码:
In[]: df_inner_join.head()

输出将类似于以下截图,其中 SQL 结果已加载到具有标记标题行的 DataFrame 中,索引列位于左侧,起始值为 0

截图

我们将继续使用 pandas 中的 merge() 函数进行剩余的练习。我在 进一步阅读 部分有一个关于该函数的详细信息参考,但使用它非常简单。一旦您将两个输入表存储为 DataFrame,您可以将它们作为参数输入到 merge() 函数中,同时指定您想要使用的连接类型,这由 how 参数控制。当您不指定参数时,默认为内部连接,它支持我们讨论的所有 SQL 连接,包括左连接、右连接和外连接。使用 merge() 函数的结果是返回一个包含源对象合并的 DataFrame。让我们通过将我们的源 SQL 表作为 DataFrame 加载来继续这个练习。

  1. 创建一个新的 DataFrame,命名为 df_user_hits,它是 tbl_user_hits 表的副本,这样我们就可以在后面的示例中使用它:
In[]: df_user_hits = pd.read_sql_query("select * from tbl_user_hits;", conn)
  1. 为了验证结果,您可以使用此代码运行 head() 函数对此 DataFrame:
In[]: df_user_hits.head()

输出将类似于以下截图,其中 SQL 结果已加载到具有标记标题行的 DataFrame 中,索引列位于左侧,起始值为 0

截图

  1. 创建一个新的 DataFrame,命名为 df_user_geo,它是 tbl_user_geo 表的副本,这样我们就可以在后面的示例中使用它:
In[]: df_user_geo = pd.read_sql_query("select * from tbl_user_geo;", conn)

输出将类似于以下截图,其中 SQL 结果已加载到具有标记标题行的 DataFrame 中,索引列位于左侧,起始值为 0

  1. 由于我们不再需要运行任何 SQL 查询并已检索所有数据,关闭数据库连接是一种最佳实践。您将运行此命令来关闭它:
In[]: conn.close()

现在我们已经将所有数据加载到 pandas DataFrame 中,我们可以通过稍微修改merge()函数中的参数来遍历不同的连接类型。所有示例的leftright参数分别为df_user_hitsdf_user_geo。连接字段是一致的,即两个 DataFrame 的userid。在这个例子中,源表使用相同的公共字段名作为它们的唯一标识符,这很有帮助。

我们将传递给merge()函数的最后一个参数名为how,它决定了将执行哪种类型的连接。我们将从我最喜欢的开始,即左连接。

  1. 使用 pandas 的merge()函数创建两个 DataFrame 之间的左连接,创建一个新的 DataFrame。在下一行中,您可以包含新的 DataFrame 名称,这将输出类似于使用head()函数的结果:
In[]: df_left_join=pd.merge(left=df_user_hits, right=df_user_geo, how='left', left_on='userid', right_on='userid')
df_left_join

输出将类似于以下截图,其中merge()的结果已加载到名为df_left_join的新 DataFrame 中,带有标签的表头行,索引列位于左侧,起始值为0

预期结果,仅显示来自df_user_hitsuserid

注意 SQL 和 pandas 之间的区别,其中空白null()值被NaN(代表Not a Number)所替换。

接下来,我们将创建一个右连接,这与我们之前的语法略有不同。

  1. 使用 pandas 的merge()函数创建两个 DataFrame 之间的右连接,创建一个新的 DataFrame。在下一行中,您可以包含新的 DataFrame 名称,这将输出类似于使用head()函数的结果:
In[]: df_right_join=pd.merge(left=df_user_hits, right=df_user_geo, how='right', left_on='userid', right_on='userid')
df_right_join

输出将类似于以下截图,其中合并结果已加载到名为df_right_join的新 DataFrame 中,带有标签的表头行,索引列位于左侧,起始值为0

预期结果,仅显示来自df_user_geouserid

与我们的 SQL 示例类似,我们可以通过使用默认值来执行内部连接,即在将how参数传递给merge()函数时省略它。

如果您想确保定义所使用的连接类型,您始终可以显式包含how参数。

  1. 使用 pandas 的merge()函数创建两个 DataFrame 之间的内部连接,创建一个新的 DataFrame。在下一行中,您可以包含新的 DataFrame 名称,这将输出类似于使用head()函数的结果:
In[]: df_inner_join=pd.merge(left=df_user_hits, right=df_user_geo, left_on='userid', right_on='userid')
df_inner_join

输出将类似于以下截图,其中合并结果已加载到名为 df_inner_join 的新 DataFrame 中,带有标签的标题行,索引列从左侧开始,值为 0

图片

预期结果,仅显示存在于两个 DataFrame 中的 userid

最后,让我们通过添加 how 参数并包含 outer 值来使用 merge() 函数创建一个外连接:

  1. 创建一个新的 DataFrame,其中包含 pandas merge() 函数创建的两个 DataFrame 之间的外连接的结果。在下一行,您可以包含新的 DataFrame 名称,这将输出类似于使用 head() 函数的结果:
In[]: df_outer_join=pd.merge(left=df_user_hits, right=df_user_geo, how='outer', left_on='userid', right_on='userid')
df_outer_join

输出将类似于以下截图,其中合并结果已加载到名为 df_outer_join 的新 DataFrame 中,带有标签的标题行,索引列从左侧开始,值为 0

图片

预期结果,将显示存在于任一 DataFrame 中的所有 userid 实例:

太棒了,我们已经成功重新创建了本章迄今为止讨论的所有连接类型。无论您更习惯使用 SQL 还是 pandas,将数据集连接起来的能力是一项强大的技能,并且显著提高了您的数据素养。

解释数据聚合

数据聚合是您日常生活中的一个部分,您可能甚至没有意识到它。当您查看使用一到五颗星的餐厅评论时,或者在亚马逊上购买一个有数千条客户评价的商品时,这两个例子都是数据聚合。数据聚合可以定义为基于显著更大的细节的摘要。在 SQL 中,聚合是在一个或多个表上应用 groupby 命令时发生的,这包括对一个或多个字段进行的统计计算,如总和、平均值、最小值或最大值。

理解数据的粒度

计算的聚合被称为度量。当您按一个或多个字段分组以获取它们的唯一值时,它们被分类为维度。

因此,这应该听起来都很熟悉,因为我们已经在 第五章,在 Python 中收集和加载数据,以及 第六章,可视化和处理时间序列数据 中介绍了维度和度量的概念,因为它是数据建模和可视化的基础。为了加强这一概念,让我们看看表格或 DataFrame 如何通过视觉方式汇总。如图所示,任何行数和列数的输入表格都可以通过多种不同的聚合类型进行汇总和简化,例如按用户或按日期:

图片

那么,为什么聚合对于分析来说是必需的和重要的呢?首先,正如你在前面的屏幕截图中所看到的,形状和大小显著减少,这有助于大型数据集便于人类或机器消费。

对于人类来说,当数据聚合时,它变得易于查看和理解,因为人们不必视觉上筛选数千行和列。对于机器来说,在形状和大小上减少非常大的数据源有助于减少文件大小、内存占用和数据处理时的输入/输出。当你看到结构化数据的大小以千兆字节GB)为单位时,影响该大小的主要因素(无论是文件、DataFrame 还是数据库表)是数据的密度。数据的密度由表或源文件中的行和列中的每个数据点值定义。当你聚合数据时,数据量将显著减少,因为一个或多个包含唯一值的字段被移除。

例如,如果你有一个高交易量的事务表,按时间戳有数百万个不同的userid值,如果用户每几秒钟执行一个动作或事件,每天的记录数可能达到数千万。如果你的分析需求是衡量每天用户的平均数量,你可以创建一个每天有一行的每日快照表。因此,简单的聚合将数千万行减少到每天一行!那么,问题在哪里呢?首先,我们失去了每个用户每天执行的动作的所有粒度。另一个因素是处理和管理聚合表的时间。任何数据工程师都会告诉你,停机时间和错误是会发生的,所以他们必须保持源表和任何聚合表同步,并在源表发生变化时恢复。

为了解决任何粒度的损失,你可以根据不同的字段/维度创建其他聚合,但如果你在聚合表创建后添加更多属性,可能会产生其他问题。例如,如果你对按城市和州平均每日用户计数进行一年的聚合表快照,然后想按邮政编码进行分析,你可能需要重新处理以回填所有历史数据,或者拥有两种不同的平均粒度,这些粒度在特定日期前后发生变化。

数据聚合的实际应用

因此,现在我们更好地理解了聚合的存在及其原因,让我们来了解一下如何在 SQL 和 pandas 中创建它们。在这个例子中,我们将使用名为tbl_user_geo_hits的用户点击数据的一个类似版本。这个表包含了我们之前一直在使用的来源的合并记录,但现在我们可以专注于聚合和groupby语法。SQL 语言可能很复杂,但足够强大,可以同时处理连接和聚合,但我发现分解这个过程会使学习更容易。此外,由于数据量很大和/或需要报告,通常会有持久化的表或视图(类似于表但由一个或多个连接派生的数据库对象)可用。

我们将首先启动一个新的 Jupyter 笔记本,并将其命名为ch_08_sql_and_pandas_group_by

  1. 加载 SQLite 数据库连接:
In[]: import sqlite3

这个库应该已经通过 Anaconda 可用。有关设置环境的帮助,请参阅第二章,Python 和 Jupyter Notebook 安装概述

  1. 接下来,我们需要将一个连接分配给名为conn的变量,并指向数据库文件的位置,该文件名为user_hits.db。由于我们在之前的In[]行中已经导入了sqlite3库,我们可以使用这个内置函数与数据库进行通信:
In[]: conn = sqlite3.connect('user_hits.db')

确保您已将user_hits.db文件复制到正确的 Jupyter 文件夹目录中,以避免连接错误。

  1. 导入pandas库:
In[]: import pandas as pd
  1. 要运行 SQL 语句并将结果分配给 DataFrame,我们必须运行这一行代码。pandas库包含一个read_sql_query函数,这使得使用 SQL 与数据库通信变得更加容易。它需要一个连接参数,我们在之前的步骤中将其命名为conn。我们将结果分配给一个新的 DataFrame,命名为df_user_geo_hits,以便更容易识别:
In[]: df_user_geo_hits = pd.read_sql_query("select * from tbl_user_geo_hits;", conn)
  1. 为了验证结果,你可以使用以下代码运行head()函数对此 DataFrame 进行操作:
In[]: df_user_geo_hits.head(10)

输出将类似于以下截图,其中合并结果已加载到名为df_user_geo_hits的新 DataFrame 中,带有标签的标题行,左侧的索引列从0开始:

图片

因此,我们有了用户点击量数据,并且通过将其加载到 DataFrame 中来预览了这些数据。使用按组功能的优势在于,它允许我们针对数据提出具体问题,并通过在 SQL 语法中使用的维度和聚合上进行一些轻微调整来返回答案。在所有时间范围内,按城市和州发生了多少用户点击量?为了回答这个问题,让我们确定所需的维度和度量。维度是城市,度量是通过计算记录出现的频率的聚合,这由count (*)函数表示。由于我们需要的所有这些信息都在一个表中,因此不需要进行连接。

如果我们只包括 城市 这一个维度,那么我们就会合并任何重复的 城市 名称,并错误地表示数据。例如,多佛 这个城市存在于多个州,如特拉华和新泽西州。这就是为什么如果不包括正确的字段在按组聚合中,数据可能会丢失粒度。

  1. 要运行 SQL 语句并将结果分配给 DataFrame,我们必须运行这一行代码。pandas 库包含一个 read_sql_query() 函数,这使得使用 SQL 与数据库通信变得更容易。它需要一个连接参数,我们在前面的步骤中将其命名为 conn。我们将结果分配给一个新的 DataFrame,命名为 df_groupby_SQL,以便更容易识别:
In[]: df_groupby_SQL=pd.read_sql_query("select city, state, count(*) as hits from tbl_user_geo_hits group by 1, 2;", conn)
  1. 为了验证结果,你可以使用以下代码对此 DataFrame 运行 head() 函数:
[]: df_groupby_SQL.head()

输出将类似于以下截图,其中合并结果已加载到名为 df_groupby_SQL 的新 DataFrame 中,带有标签的标题行从左侧的 0 值开始:

图片

SQL 语言支持快捷方式,因此我们使用 group by 1, 2 来表示 城市 的二维字段。它还允许字段名称的别名,因此使用 count(*) as hits 来使其更容易表示。

要使用 pandas 重新创建 SQL 结果,我们可以使用我们加载的 DataFrame,并使用 groupby() 函数和一些参数。在我们的例子中,我们传递我们想要按其分组列的名称,即 城市。度量将与之前相似,通过包括 .count(),我们包括想要进行聚合的字段,由于我们是在计数频率,因此可以是任何字段。我们使用 userid,因为分析集中在用户上。

  1. 要运行 SQL 语句并将结果分配给 DataFrame,我们必须运行这一行代码。pandas 库包含一个 read_sql_query 函数,这使得使用 SQL 与数据库通信变得更容易。它需要一个连接参数,我们在前面的步骤中将其命名为 conn。我们将结果分配给一个新的 DataFrame,命名为 df_groupby_city_state,以便更容易识别:
In[]: df_groupby_city_state=df_user_geo_hits.groupby(["city", "state"]) ["userid"].count()
  1. 为了验证结果,你可以使用以下代码对这个 DataFrame 运行head()函数:
In[]: df_groupby_city_state.head()

输出将类似于以下截图,其中合并结果已加载到名为df_groupby_city_state的新 DataFrame 中:

图片

  1. 由于我们不再需要运行任何 SQL 查询并已检索所有数据,关闭数据库连接是一个好习惯。你可以运行以下命令来关闭它:
In[]: conn.close()

因此,我们已经展示了通过在pandas和 SQL 中使用聚合组来总结数据的力量。聚合可以针对单个表或多个表之间的连接执行。使用 SQL 或pandas的共同之处在于定义维度和度量,这些是从 DataFrame 或 SQL 对象(表、连接或视图)中可用的字段抽象出来的。到目前为止,我们只使用了一种度量类型,即计数,来触及表面。还有更多用于数据分析的统计函数。因此,接下来,我们将探讨均值、中位数和众数之间的区别。

概率统计和异常值

在第五章“在 Python 中收集和加载数据”中,我们讨论了处理数据时基本统计学的必要性。现在,让我们探讨统计学中均值、中位数和众数之间的区别,以及它们在数据分析中的应用。均值或平均值是指将一系列数值的总和除以这些数值的数量。均值或平均值是分析中的一个度量,通常用于衡量一段时间内的表现,并为每个时间段定义一个比较基准。

例如,你经常在新闻中看到平均每日气温——它是如何计算的?根据你的地理位置,天气记录将使用特定的增量来记录温度,例如小时。例如,国家海洋和大气管理局NOAA)使用站点和科学方法来计算每一天和每个地点的最小和最大温度值。然后,这些个别记录被用来创建平均月温度和按月计算的 30 年平均值。

确保你理解你的数据来源,尤其是在处理平均值时,因为平均值的平均值将限制你如何处理数据,并且很可能是指标误用。理想情况下,拥有最低级别的细节,这样你就可以根据所需的任何时间段重新计算平均值。

对于我们在本章中一直在使用的示例,计算平均值或均值将提供一个我们可以用于比较的标准。因此,如果我们想知道每天的平均点击次数,我们只需计算所有记录并除以日期值的唯一计数。从tbl_user_geo_hits数据中,平均值为每天 2.3 次点击,因为你共有七个记录,三天是不同的。现在我们可以用这个作为试金石来衡量每天与该平均值相比是否有显著的增加或减少。

中位数,是一系列数值中的中心值,可以通过找到中间值来确定,其中 50%的数值大于或小于该特定数据值。通常将这些值按顺序排列,以便更容易识别中间值。确定平均值对于测量中心趋势很有用,这有助于你识别数据值的分布情况,并且不受异常数据点的影响。我们将在第九章绘图、可视化和讲故事中探讨分布的形状。

最后,我们有众数,它是系列中出现频率最高的值。众数在分析和分析中不太常用,但在识别数据中的异常值时很有用。什么是异常值?从数学上讲,它可能被定义为与平均值多个标准差的数据值。实际上,它是指与数据中的其他值相比,某个值或汇总值偏离正常模式的情况。一种识别异常值的好方法是绘制值与正态分布或通常所说的钟形曲线的对比图,以下图中用虚线黑色线表示:

图片

如前图所示,分布线遵循基于平均值和标准差形状。一个真正的正态分布将具有平均值、中位数和众数都相等的数据值。在曲线顶部的峰值左右,基于一个、两个或三个标准差。加减三个标准差的数据值将代表仅占所有数据值总人口的 99.7%。

从实际的角度来说,我们说的是所有数据中不到百分之一的数据会接近最小值和最大值。你的数据是否呈正态分布可能并不重要,但了解这一点有助于识别数据值的形状。在先前的例子中,有一些红色点明显低于和高于线,这些被认为是异常值。与正态分布相比,这种异常值的视觉表示将帮助数据分析师与他人就数据进行交流。这是数据素养的力量,其中视觉表示可以激发对话,并帮助回答关于预期或正常情况以及异常或异常值的问题。

摘要

恭喜,我们已经覆盖了在 SQL 和 Python 中使用 pandas DataFrames 进行数据连接和合并的基础知识。在整个过程中,我们讨论了实际例子,说明了应该使用哪些连接类型,以及为什么应该针对用户点击数据使用它们。通过混合多个数据表来丰富我们的数据,可以让我们进行更深入的分析,并能够回答更多关于原始单一数据源的问题。在了解连接和 merge() 函数之后,我们揭示了数据聚合的优缺点。我们通过实际例子介绍了在 SQL 和 DataFrames 中使用 groupby 功能。我们还探讨了统计函数与均值、中位数和众数之间的区别,以及通过将结果与正态分布的钟形曲线进行比较来寻找数据中异常值的技巧。

在下一章中,我们将回到使用绘图库和可视化数据。

进一步阅读

关于本章主题的更多信息,您可以参考以下链接:

绘图、可视化和讲故事

本章将教你如何通过探索额外的图表选项,如直方图、箱线图和散点图来可视化数据,以提升你的数据素养技能。数据讲故事始于理解数字之间的关系,因此我们将学习分布曲线及其在分析中的应用。在分析数据的发现阶段,你将学习如何识别异常值和模式,以及可视化地理数据的最佳实践。我们将通过学习相关性因果关系的区别来结束本章。

本章我们将涵盖以下主题:

  • 解释分布分析

  • 理解异常值和趋势

  • 地理分析技术和技巧

  • 在数据中寻找模式

第十二章:技术要求

本书 GitHub 仓库地址为github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter09

您可以从www.anaconda.com/products/individual下载并安装所需的软件。

解释分布分析

我无法回忆起历史上数据、统计学和科学像今天这样占据人们日常生活的情况。新闻周期正在实时呈现危机,人类行为的变化和社会规范正在被重新定义。在我撰写这本书的时候,曲线平滑的概念已经变得主流,并因冠状病毒(COVID-19)大流行而成为全球通用的概念。你可能已经看到了以下图表中展示的内容,该图表是从疾病控制与预防中心CDC)改编的。这类可视化通常用于传达预防疾病传播的重要性。以下可视化有两个曲线,一个用黄色标注为未采取干预措施,另一个用蓝色标注为采取预防措施的“曲线平滑”。有一条虚线参考线,标注为医疗保健能力,用于比较两条曲线的相对能力。从数据素养的角度来看,我们可以将它们识别为并排显示的分布曲线,以测量y轴上的每日病例数,其共同维度为持续时间,标注为x轴上的自首例病例以来天数。分布曲线在数据分析中很常见,并直观地表示单个变量的数值数据:

图片

为了参考,让我们回顾最著名的分布曲线,它被称为高斯分布正态分布钟形曲线,因为它与物理钟形的形状在视觉上相似。在正态分布中,数值将表示在最高点所在的线上,最高点代表样本或整个数值群体的平均值或平均数。当线向两端延伸时,它显示了数值与平均值之间的差异。这种数值从平均值出发的扩散或分散通常被称为标准差SD)。在完美的分布中,数值将落在加减一个、两个或三个标准差范围内,这会在线形上产生对称性。

关于正态分布,有一些有趣的事实需要了解:

  • 大约 68%的数据值落在加减一个标准差范围内。

  • 96%的数据值落在加减两个标准差之外。

  • 99.7%的数据值落在加减三个标准差之内。

  • 计算出的平均值、中位数和众数都相等。

  • 数据在平均值左侧和右侧的分布是 50/50%。

回到我们的例子,在第一个标记为未采取干预措施的曲线中,数字实际上每隔几天就会翻倍,这使得曲线在接近最高点时变得非常陡峭。第二个曲线,被标记为采取预防措施“平缓曲线”,帮助数据消费者可视化停止病毒传播的重要性,因为曲线的高度已经显著降低。我相信这张图表之所以对大众传播变得相关,是因为它以简洁的方式解释了分布曲线,而不涉及背后的统计数据。即使不显示曲线背后的数据,任何人都可以直观地理解这一关键信息的意义。

在撰写这本书的这一时刻,我不知道全世界的人们是否已经成功实现了降低 COVID-19 死亡率的目标。此外,目前尚不清楚是否采取了足够的预防措施,如保持社交距离,是否有助于平缓曲线。全世界已有许多人遭受了 COVID-19 大流行的悲剧。我对所有遭受苦难的人表示衷心的慰问。

KYD

要查看 COVID-19 数据的分布情况,我提供了一个快照 CSV 文件,该文件可在本书的 GitHub 仓库中找到。为了支持我们的数据分析师了解你的数据KYD)口号,我将提供一些关于数据收集方式和格式的额外信息。COVID-19 Cases.csv文件是从权威的 COVID-19 开源数据源收集的。在进一步阅读部分提供了一个由约翰霍普金斯大学系统科学与工程中心CSSE)维护的 GitHub 仓库。疾病控制与预防中心(CDC)也一直在为公共利益分发 COVID-19 数据。

CSV 文件中前几条记录的样本将类似于以下截图,该截图是从进一步阅读部分找到的权威来源获取的:

截图

这个数据源包含按国家每日的 COVID-19 病例快照。我们分析中使用的关键字段如下:

  • Date字段是以M/D/YYYY格式表示的,是确认 COVID-19 病例的日期。

  • Country_Region字段是追踪 COVID-19 病例的原始国家。

  • Cases字段是按国家和日期累计的 COVID-19 病例数量。

  • Difference字段是按国家和日期每日的 COVID-19 病例数。

  • Case_Type字段是指定给每个值的病例类型,可以是ConfirmedDeaths

从这个数据源中,我们可以回答关于数据的多个问题,但需要一些过滤来隔离按CountryDateCase_Type记录。

曲线形状

现在我们对数据有了更多信息,我们可以启动一个新的 Jupyter Notebook 进行数据分析,以识别曲线的形状。

启动一个新的 Jupyter Notebook,并将其命名为ch_09_exercises。要将 CSV 数据导入到pandas DataFrame 中,以便我们可以创建直方图,我们使用以下步骤:

  1. 通过在 Jupyter Notebook 中添加以下代码导入以下库,并运行该单元格。你可以自由地创建自己的 Notebook 来跟随;我还在 GitHub 上放置了一个副本以供参考:
In[]: import pandas as pd
     import numpy as np
      import matplotlib.pyplot as plt
      %matplotlib inline

这些库应该已经通过 Anaconda 可用。如果你需要帮助设置环境,请参考第二章,Python 和 Jupyter Notebook 的概述和安装%matplotlib inline是一个魔法命令,在运行单元格后,它可以在你的 Jupyter Notebook 中显示可视化结果。

  1. 接下来,我们通过导入 CSV 文件创建一个新的 DataFrame:
In[]: covid_df = pd.read_csv("COVID-19 Cases.csv", header=0)

确保你已经将COVID-19 Cases.csv文件复制到正确的 Jupyter 文件夹目录中,以避免连接错误。

  1. 为了验证 DataFrame 是否正确加载,我们可以运行head()函数来显示前几条记录:
In[]: covid_df.head()

输出将类似于以下截图,其中源 CSV 文件已被加载到 DataFrame 中,带有标记的标题行,索引列位于左侧,起始值为 0

  1. 接下来,我们将通过从源数据创建一个新的 DataFrame 并对其应用一些过滤器来隔离我们想要关注的特定数据。我们想要隔离满足以下所有条件的记录。首先,每日 Difference 计数大于零。其次,Case_Type 应为 Confirmed。最后,Country_Region 应仅限于 Italy
In[]: df_results = covid_df[(covid_df.Difference >0) & (covid_df.Case_Type == 'Confirmed') & (covid_df.Country_Region == 'Italy')]

默认情况下,新的 df_results DataFrame 不会在 Jupyter Notebook 中显示结果。

  1. 要查看排序后的结果,我们运行以下命令:
In[]: df_results.sort_values(by='Cases', ascending=False)

输出应类似于以下截图,其中显示了一个新的 df_results DataFrame,其值按 Cases 降序排序:

  1. 现在,我们想要通过以下命令使用默认的 hist() 图表来可视化显示 Difference 列中值的分布:
In[]: df_results.hist(column='Difference');

输出将类似于以下截图,其中显示的值以默认直方图图表的形式呈现。默认设置将数据值分成相等大小的区间,这些区间显示在 x 轴上。每个区间内值的出现频率或计数由 y 轴测量:

那么,这个直方图图表最初告诉我们关于前面图表中数据的什么信息?首先,由于最高的柱状图是左侧的第一个柱状图,因此大多数数据值都小于 1,000。其次,根据形状,我们知道数据不是正态分布,因为我们预计最频繁的结果应该在中间,接近数据的平均值。这种分析如何应用于 COVID-19 数据本身?这种形状是好的,因为每日增加的数量可能要大得多。

要了解更多关于此数据形状的细节,我们可以使用 describe() 函数针对 DataFrame 中的此特定列进行操作。

  1. 使用 describe() 函数对此 DataFrame 进行操作以查看摘要统计信息。我们可以通过在方括号中明确传递它以及双引号中的列/字段名称来查看一个列:
In[]: df_results["Difference"].describe()

输出将类似于以下截图,其中显示了此特定字段中数据的摘要统计信息:

在前面的屏幕截图中,我们已识别了一些关键统计数据,以帮助更好地理解此数据的形状。在摘要表中,有33个值被标识为count,均值为1937.181818std(标准差)为2132.965299。这三十三个值的范围从16557,分别由minmax标识。由于标准差值如此之高,我们知道这些数字分布得很广。

值将以float64数据类型显示,无论源数值的精度如何,都将保留六位小数。

25%、50%和 75%的标签返回该字段中值系列的相应百分位数。这些值也被称为四分位距IRQ),其中 50%或第二四分位数等于中位数。将数据按四分位数划分,为数据值创建相等的区间或桶,有助于我们了解数值的分布情况。如果一个多数值落入一个特定的桶中,你就知道数据分布不均匀。以我们的例子为例,我们的均值和中位数之间有一个很大的差距(1937 与 778),因此我们可以将此数据分类为偏斜。数据中的偏斜有助于理解分布曲线或直方图的视觉形状不是对称的。为了帮助您记住分布类型和偏斜,我在以下图表中总结了它们。当均值大于中位数时,它会有正偏斜,而当相反的情况发生时,存在负偏斜。正如以下图表中每个视觉趋势顶部所描述的,偏斜的类型(负或正)与均值和中位数值直接相关。当所有均值、中位数和众数值相等时,你就有了一个对称的分布:

图片

从数据分析的角度来看,定义这些关键统计数据有助于我们理解数据值的分布情况。计算均值、中位数和众数相对于你的数据值,统称为集中趋势的度量,这在第八章“理解连接、关系和聚合”中已介绍。集中趋势的一个重要且实用的应用是在数据科学模型中。在利用历史数据的预测回归模型中,计算预测的能力基于找到分布曲线的最佳拟合。如果数据有显著的正面或负面偏斜,且具有长尾,即值从中心趋势偏离多个标准差,算法的准确性就会降低。

因此,我们现在理解了计算中心趋势的重要性以及如何将对称数据以正态分布曲线的形式进行视觉表示。正态分布,也称为高斯分布和钟形曲线,当均值(平均值)等于中位数(中间值)且等于众数(最频繁出现的值)时发生。我发现添加一条正态分布线作为参考,与图表中的实际数据结果进行比较是有用的。这有助于分析结果的消费者从视觉上比较数据的理想形状与实际结果。那么,是什么原因导致数据偏斜或无法适应正态分布?作为一名数据分析师,你的任务是找出原因,第一步是隔离数据中可能存在的异常值。我们将在下一节中通过了解异常值和趋势来讨论这个问题。

理解异常值和趋势

寻找异常值首先从分布曲线开始,但需要额外的技术,我们将一起探讨。此外,不要低估软技能的需求,你必须与他人沟通,以更好地理解为什么你的数据中存在异常值。异常值通常是指与数据中的其他值显著不同的一个或多个数据值。根据所使用的数据可视化方法,发现数据中的异常值很容易,但在许多情况下,尤其是在数据量非常大时,它们在数据聚合时可能会被掩盖。如果你还记得第七章,探索数据集的清理、精炼和合并,我们与用户为网站创建的点击量进行了工作。掩盖异常值的一个好例子是当用户点击量按日期聚合时。如果一个特定用户每天有 1,000 次点击,而平均值为 2,那么在按周聚合数据后,很难识别出这个异常用户。那么,在一系列数据值中,异常值在视觉上看起来是什么样子?一个好的方法就是使用箱线图,因为它可以直观地表示describe()函数中找到的数据:

图片

如前图所示,箱线隔离了 25%、50%和 75%的四分位数,以及值的最大/最小范围显示在最极端的垂直线上。箱线与最大/最小线之间的空间被称为箱线图的触须。如果你看到一个加号(+)显示,它们被称为飞线,在这个图表类型中是异常值。

箱线图可以水平或垂直显示,并且可以包括多个维度,这样你就可以比较它们之间的分布。

让我们继续分析我们现有的数据集,看看它是如何使用箱线图进行可视化的。与先前的例子类似,我们将从源中加载所有数据到一个单独的 DataFrame 中,然后使用过滤器创建一个子 DataFrame。我们将继续使用ch_09_exercises Jupyter Notebook:

  1. 通过在您的 Jupyter Notebook 中添加以下命令来导入以下库,并运行该单元格。您可以自由地创建自己的笔记本来跟随;我已经在 GitHub 上放置了一个副本供参考:
In[]: import pandas as pd
      import numpy as np
      import matplotlib.pyplot as plt
      %matplotlib inline
  1. 通过导入 CSV 文件创建一个新的 DataFrame:
In[]: covid_df = pd.read_csv("COVID-19 Cases.csv", header=0)
  1. 要验证 DataFrame 已正确加载,我们可以运行head()函数来显示前几条记录:
In[]: covid_df.head()

输出将类似于以下截图,其中源 CSV 文件已加载到 DataFrame 中,带有标记的标题行,索引列从左侧的值0开始:

图片

与之前的练习类似,我们将通过从源数据创建一个新的 DataFrame 并对其应用一些过滤器来隔离我们想要关注的焦点数据。我们想要隔离满足以下所有条件的记录。首先,每日Difference计数大于零。其次,Case_Type应该是Confirmed。最后,我们使用管道符号|创建一个or条件,允许有多个Country_Region

In[]: df_results = covid_df[(covid_df.Difference >0) & (covid_df.Case_Type == 'Confirmed') & ((covid_df.Country_Region == 'Italy') | (covid_df.Country_Region == 'Spain') | (covid_df.Country_Region == 'Germany'))]

默认情况下,新的df_results DataFrame 不会在 Jupyter Notebook 中显示结果。

  1. 要查看结果,我们运行以下命令:
In[]: df_results.head()

输出应类似于以下截图,其中将显示一个新的df_results DataFrame:

图片

  1. 要按国家显示箱线图,我们使用以下命令。boxplot()有几个参数,例如by=,它允许我们按Country_Region分组数据。我们还包括column=来隔离Difference字段中的值。最后,我们传入grid=False来关闭图表中的网格线:
In[]: df_results.boxplot(by='Country_Region', column=['Difference'], grid=False)

输出将类似于以下截图,其中将显示箱线图:

图片

因此,将数据限制在仅三个国家,使我们能够缩小分析范围,并且将数据在箱线图图表中并排展示,如图中所示,使我们能够直观地比较结果。首先,我们注意到几个箱的大小,如果您还记得,这些是数据的四分位数,并且根据国家的不同而大小不同。德国有一个较小的箱,更接近正方形而不是长方形,这通常告诉我们数据的分布非常紧密。我们可以在图表中识别出的另一个观察结果是,我们显示了多个加号符号(+);这些突出显示了德国和西班牙国家中存在的异常值。

分析按国家和其他与地理相关的属性的数据,是当今数据分析师的一个常见需求。接下来,我们将探讨与在地图和空间数据中可视化数据相关的最佳实践,这被称为地理分析。

地理分析技术和技巧

对于数据分析师来说,地理分析的概念是一个相对较新的技术,它应用于空间数据以了解数据在地理上的位置。然而,制图学,即地图的研究,已经存在几个世纪,传统上需要培训、专业知识和专门的软件来通过位置提供数据见解。今天,有多个附加模块和软件可用于创建图表和可视化,这些图表和可视化使用地图以令人兴奋的方式可视化数据,从而提供不同的视角。

首先,你需要了解你拥有的数据的粒度。除非源系统是为了捕获该信息而构建的,否则在源数据中拥有精确的经纬度是一个奢侈。例如,移动应用源数据通常会有这种详细程度,因为智能手机可以追踪你的位置。然而,如果我们回到我们的 COVID-19 源数据,个别病例的最低详细程度是按Province_State来划分的,因此你失去了显示低于该级别的数据的能力。

接下来,如果你的源数据不包含经纬度值,你必须添加它们,这听起来很简单,但通常有一些挑战需要克服。当你分析数据时,确保它在特定字段(如CountryCityParcelZip Code)中具有一致性和一致性。如果存在Country值,它们是否有国际标准化组织(ISO)代码,这样你就可以将数据与常用来源连接起来?如果有,我在进一步阅读部分包含了一个链接到世界银行数据源。

最后,我发现包含一个世界或区域地图作为良好数据分析解决方案的补充,但不是替代品。在许多情况下,即使有颜色渐变的单一地图图表,也无法提供足够的答案来回答常见问题。例如,让我们看看以下截图,它显示了在 HealthMap 网站上找到的 COVID-19 疫情全球地图:

图片

如前一张截图所示,图表右下角的图例通过气泡的颜色和大小提供了关于疫情按地区分布的见解。如果你将这个可视化视为一个静态地图,它会让观众对气泡与颜色的重叠产生更多疑问。例如,如何区分像纽约和费城这样紧密聚集的城市之间的细节?此外,使用这个地理分析图表的消费者可能想知道病例数量是否在每个地区都在加速,这一点并不明确。

然而,一旦你访问了网站,这些批评就会改变,因为网站允许用户与地图进行交互。

当你使用为 COVID-19 疫情创建的 HealthMap 网站时,你将获得一个提供以下功能的解决方案:

  • 缩放功能,可以钻取到最低级别的细节,并缩放以比较不同位置的成果

  • 鼠标悬停在提供按位置汇总计数以及所选位置或焦点上下文的圆圈上

  • 一个时间线功能,使用户能够看到结果的前后变化

一旦你欣赏了在地理分析数据中拥有任何或所有这些功能,你期望你在作为数据生产者构建的任何解决方案中也能得到这些功能。如果你缺少数据或创建它的工具,我建议创建一个补充图表来支持它。例如,在下面的屏幕截图中,我们有一个按国家降序排列的水平条形图:

图片

图表顶部的图例表明了所使用的度量标准,标题为按国家病例数,以帮助数据消费者轻松回答诸如截至特定日期哪个国家病例数最多等问题。当你将多个相互补充的图表放在一起时,它们为地理数据的消费者提供了上下文。

确保这些图表背后的数据源之间的公共连接键是一致的。如果你在地图上选择了一个国家,条形图应该过滤以匹配任何日期选择。

使用多个图表协同工作有助于用信息讲故事,与观众建立联系。今天的数据消费者很复杂,反复讲述同样的故事将不会有效。我建议使用时间久远的技巧,比如在故事中有一个教训或寓意,这样故事才能很好地发挥作用,你应该感到有力量调整以包括你自己的个人风格。

我从艺术和学习更多关于他们技艺的大师们中找到创意灵感。例如,艺术家巴勃罗·毕加索因其蓝色时期的作品而闻名,这一时期定义了他生命中一个时期,在这个时期,他画的不同主题中普遍使用了蓝色变体的主要颜色。这个时期持续了几年,反映了他在抑郁和财务困境中生活的个人挣扎。相比之下,COVID-19 大流行正在给全世界的人们带来个人挣扎。与 COVID-19 相关的死亡率高导致全球金融困境和无数个人损失的报道。毕加索在他的一生中创作了惊人的作品量,超过 10 万件,在 70 多年的时间里。即使在情感困扰的时候,毕加索也继续找到力量去创作新的艺术作品并精通他的技艺。我能够理解他在这个大流行期间所面临的挣扎,并受到启发,花时间在我的数据技艺上,帮助我度过这些艰难时期。

使用数据讲故事的力量成为建立与观众信任的关键技能,使他们能够理解信息。使用数据可视化可以打破在处理数据时可能存在的技术障碍。作为一个精通数据素养的人,您现在拥有了使用数据创建自己故事所需的额外技能。

现在您已经了解了在数据故事讲述中有效的地理技术,让我们将注意力集中在识别数据中的模式上。

在数据中寻找模式

现在我们对分布曲线和识别数据中可能存在的异常值有了更好的理解,让我们来分析如何找到数据中的模式。根据我的经验,随着您处理越来越多的数据,您将开始发展一种第六感,这有助于您更快地识别模式,例如,以下图表可能看起来像是一串随机的数字,其中没有明显的区分模式,直到您对其进行一些小的修改使其更容易识别:

截图

将数据排序后,您可以查看数据值中存在的分组和聚类。在这种情况下,我们有数字配对,直到您将它们一起排序之前并不明显。通过快速排序,我们现在可以看到所有的数字都是重复的,如下面的图表所示:

截图

为了使这个观点更加明确,看看以下图表,其中前面两个图表中的相同数字现在按对进行着色,这创建了一个模式,使其更容易视觉识别:

截图

数据可视化和图表将帮助您识别这些模式,但根据数据的不同,某些图表比其他图表更适合。例如,为了查看数据随时间的变化模式,折线图或柱状图可以更好地显示趋势,并帮助您在数据中创建可识别的模式。以下截图是一个很好的例子,它是一个柱状图,y轴上的度量单位是点击次数x轴上显示的是日期维度,以M/DD/YYYY-DDD格式的dtype显示星期几:

截图

在前面的截图中,我们有一个趋势图,显示了每个日期的网页点击使用模式。当您查看排序后的数据时,更明显的是每几天自然出现的峰值和谷值。即使您不了解其背后的所有细节,这个模式表明我们的工作日数据使用量高于周末,我用不同的颜色突出显示,使其更明显。

在分析和识别模式的过程中,您将发现自己对数据得出结论。这样做是自然的,如果基于业务或领域专业知识,则是可行的。如果您在数据中发现适用于多个维度或变量的模式,您可以确定它们之间的相关性。相关性在数据中很常见,尤其是在同一时间段上叠加模式时。更正式的定义是,当一个变量或一系列值增加或减少时,第二个变量将平行跟随。两个值之间相关性的一个常见例子是冰淇淋店的销售额和天气。如果天气有雪或大雨或很冷,冰淇淋的销售额通常会较低。如果天气温暖且阳光明媚,销售额就会较高。基于这些信息,您可以说在同一时间段内,销售和天气这两个变量之间存在正相关

如果存在相反的关系,即两个变量之间的反向模式发生,这将被认为是负相关

要确定两个变量是否具有静态相关性,有一个称为相关系数的概念。这是一个介于 1-1 之间的测量值,用 r 表示。如果值为 0,则两个变量之间没有相关性。值越接近 1,它们之间的相关性就越正,这意味着当一个变量的值发生变化时,另一个变量将趋向于同一方向。相反,当值越接近 -1 时,负相关性会在两个变量之间创建反向关系。因此,我们能否通过数据直观地看到相关性和模式?一个好的方法就是使用散点图。

让我们继续分析现有的数据集,看看如何使用散点图进行可视化。与先前的例子类似,我们将从源中加载数据到单个 DataFrame 中,然后使用筛选器创建子集 DataFrame。在这个例子中,我们将创建两个子集以进行对比。我们将继续使用 ch_09_exercises Jupyter Notebook:

  1. 在您的 Jupyter Notebook 中添加以下命令以导入以下库,并运行该单元格。您可以自由地跟随创建自己的 Notebook;我已经在 GitHub 上放置了一个副本供参考:
In[]: import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
  1. 通过导入 CSV 文件创建一个新的 DataFrame:
In[]: covid_df = pd.read_csv("COVID-19 Cases.csv", header=0)
  1. 现在,我们将创建两个新的 DataFrame,它们将是原始源数据的子集。将它们命名为 df_results_1df_results_2 的好处是,它允许您调整如本行中使用的 Country_Region 筛选器等筛选器,而无需更改其他步骤中的任何代码:
In[]: df_results_1 = covid_df[(covid_df.Case_Type == 'Confirmed') & (covid_df.Country_Region == 'Germany')]
  1. 运行 head() 函数以验证结果:
In[]: df_results_1.head()

输出将类似于以下表格,其中显示了新的 df_results_1 DataFrame:

图片

  1. 我们将加载第二个 DataFrame,我们将使用以下命令与第一个 DataFrame 进行比较:
In[]: df_results_2 = covid_df[(covid_df.Case_Type == 'Confirmed') & (covid_df.Country_Region == 'Italy')]
  1. 运行 head() 函数以验证结果:
In[]: df_results_2.head()

输出将类似于以下表格,其中显示了新的 df_results_2 DataFrame:

命令

  1. 让我们分析每个 DataFrame 中的数据,以更好地理解它。我们使用 describe() 函数来更好地识别关键统计信息以及数据的分布情况。首先,我们查看第一个 DataFrame 的内容:
In[]: df_results_1["Cases"].describe()

输出将类似于以下截图,其中显示了结果:

截图

  1. 然后,我们查看第二个 DataFrame 的内容:
In[]: df_results_2["Cases"].describe()

输出将类似于以下截图,其中显示了结果:

可视化

基于对每个 DataFrame 运行的 describe() 函数的结果,我们有了比较的基础。首先,我们有计数,即相同的 61 个值。这在创建散点图时很重要,因为需要的数据大小必须相同。两个数据系列之间的另一个常见值是最小值,它在 0。然而,最大值是不同的,略大于两倍(29,056 对比 63,927)。接下来,我们有平均值,差异很大。第一个结果有一个四舍五入的平均值 2,707.49,第二个是 8,241.77。最后,标准差(std)也不同,因此我们知道分布曲线的形状和大小将不同。

我们想要回答的问题是:这些值是否相关?为了直观地确认这一点,我们继续通过几个简单的命令创建散点图。散点图将具有 xy 轴,并以点在网格中表示值与轴对齐的位置。

  1. 我们使用 plt.scatter() 函数创建可视化。它需要两个参数,即用逗号分隔的 xy 轴值。我们传递了在 Cases 列中找到的每个 DataFrame 的共同值系列。我们还包括标签和标题,以帮助观众理解图表:
In[]: plt.scatter(df_results_1["Cases"], df_results_2["Cases"]);
plt.title("# of Cases")
plt.xlabel("Germany Cases")
plt.ylabel("Italy Cases");

输出将类似于以下图表,其中显示了结果:

散点图

我们的散点图确实存在相关性,其中任一轴上靠近 0 的值聚集在一起,这在图表上以重叠的蓝色点表示。你还可以观察到,随着 案例数量 的增加,值之间存在自然的直线对齐。

每个相关性都伴随着一个不可避免的预期,即一个变量依赖于另一个变量,并是原因。因果关系是指一个变量直接影响另一个变量的方向。因果关系或根本原因分析是常见的分析技术。仅识别两个变量之间的因果关系是罕见的,需要更深入的分析来理解所有直接和间接影响变化的因素。首先需要理解的是,相关性并不总是等于因果关系。一个很好的例子是我们的冰淇淋店销售额和天气。我们知道吃更多的冰淇淋永远不会改善天气,但如果你纯粹地看数据,如果数据高度相关,你可能会意外地得出那种结论。在分析数据以确定相关性是否与因果关系相关时,另一个要点是基于数据的样本大小。我建议成为一个数据怀疑论者,如果数据集的总体不完整或覆盖的时间窗口很小,就质疑因果结论。最后,我们已经探讨了将数据联合起来的数据分析价值,但这也带来了得出独立存在的数据结论的机会。务必添加假设,并公开说明你如何联合数据的方法,以便相关性可以接受同行评审。

摘要

恭喜,我们现在已经学习了制作各种可视化数据分布的图表的基本技能。我们通过计算一系列数据值的标准差、均值、中位数和众数来讨论了与数据集中趋势相关的关键统计量。我们研究了正态分布以及数据值如何可能正向或负向偏斜。当数据具有对称性时,使用预测分析中的一些算法变得更容易。我们回顾了在处理数据集时常见的模式和异常值,以及如何使用箱线图来可视化异常值。

我们讨论了处理地理空间数据的最佳实践和技巧,以及如何使用它来帮助用数据讲述故事。最后,我们讨论了相关性与因果关系的区别,以及相关系数的重要性,这样你就可以理解两个变量/一系列数据值之间的关系。

在下一章中,我们将转向处理非结构化数据源,以及处理免费文本数据时的最佳实践。

进一步阅读

关于本章相关主题的更多信息,您可以访问以下链接:

第十三章

第三部分:处理非结构化大数据

现在已经使用结构化数据建立了数据分析的基础,在本节中,我们将探索非结构化社交媒体和文本数据的激动人心的前沿。在这里,我们将了解监督式自然语言处理(NLP)的各个要素以及如何使用基本的情感分析模型。最后,我们将通过一个使用开源数据提供洞察力的综合项目来结束这本书。

本节包括以下章节:

  • 第十章,探索文本数据和非结构化数据

  • 第十一章,实用情感分析

  • 第十二章,整合一切

探索文本数据和非结构化数据

需要熟练掌握结构化和非结构化数据的需求持续演变。处理结构化数据有成熟的技巧,如合并和统一数据类型,这些我们在前面的章节中已经回顾过。然而,处理非结构化数据是一个相对较新的概念,正在迅速成为数据分析中的一项必备技能。自然语言处理NLP)已经发展成为一项基本技能,因此本章介绍了可用于分析叙述自由文本的概念和工具。随着技术的进步,使用这些技术可以帮助你为非结构化数据提供透明度,这在几年前是难以发现的。

在本章中,我们将涵盖以下主题:

  • 准备处理非结构化数据

  • 词汇化解释

  • 计数单词并探索结果

  • 文本归一化技术

  • 排除分析中的词汇

第十四章:技术要求

本书在 GitHub 上的仓库地址为github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter10

您可以从www.anaconda.com/products/individual下载并安装所需的软件。

准备处理非结构化数据

现在,我们生活在一个数字时代,数据以一种在以前技术上不可能或甚至无法想象的方式融入了我们的生活。从社交媒体到移动设备,再到物联网IoT),人类正生活在通常被称为信息时代的环境中。在这个时代,关于你的数据呈指数级增长,你可以随时随地即时获取。使这一切成为可能的是人和技术相结合的结果,包括来自数据分析的演变的贡献,这在第一章,《数据分析基础》中有所介绍。

多个来源普遍预测,在未来几年内,全球产生的所有数据中将有 80%是非结构化的。如果你还记得第一章,《数据分析基础》中的内容,非结构化数据通常被定义为不提供统一性和预定义组织的信息。非结构化数据的例子包括自由文本、聊天机器人、人工智能AI)、照片、视频和音频文件。社交媒体当然产生了最高量的非结构化数据,速度和多样性。因此,我们将重点关注这些数据源,但你也可以将本章学到的概念应用于任何基于叙述的文本数据源,如帮助台票据日志或电子邮件通信。

关于处理非结构化数据的重要理解是,它本质上是混乱的。没有结构、定义的数据类型以及在数据捕获时应用的一致性,就会产生不确定性。这不应该阻止或阻止你处理数据,但你应该意识到在某些情况下,精确性将是一种奢侈。例如,句子中的单词可能会拼写错误。一个词或短语的含义可能会被错误地表达,有时上下文也会丢失。尽管存在所有这些缺点,这项技术仍在不断改进,以至于你会在日常生活中发现它的应用。例如,聊天机器人现在正在取代许多客户服务解决方案,所以你可能认为你正在与一个代理交谈,但实际上是安装了 NLP 算法的软件。

NLP 的概念已经存在了几十年。使用 NLP 概念有一些基本原理,你可能过去已经使用过,比如基于规则的条件或标签。一个基于规则的例子是,当在数据集中找到一个特定的关键词或一组单词,并使用标志字段来识别它时,类似于以下表格,其中我们有三列和五行,包括一个标题行来识别每条记录的目的:

图片

在表格中,我将关键词balanced的任何变体都改变了字体颜色,以便更容易识别。正如你可以从表格中观察到的,这个词确实有一些细微的变体,这对人类来说似乎是显而易见的,但必须编程为规则来考虑这些差异。因此,为了识别任何关键词的变体,例如它是否是大写或变形的,必须使用条件逻辑来考虑。在这个例子中,使用了一个名为key_word_found_flag的条件字段来确定短语字段是否包含关键词,它使用值1来识别一个真实案例。

基于规则的 NLP 已经从早期使用基本通配符搜索和 ASCII 字符识别的概念发展到了机器学习的统计模型。我并不打算深入探讨 NLP 所使用的数学模型的细节,但我鼓励你探索这个主题。在本章中,我将专注于使用预定义的 NLP 库,而不对其进行调整或训练模型。目的是帮助你建立一些信心,了解何时、何地以及如何使用它。在过去的几年里,我对利用 NLP 的解决方案背后的准确性水平印象深刻,但我认识到它是一个不断发展的技术。前几天当我与 Alexa 互动时,它告诉我无论我如何措辞,它都无法理解我的问题,这让我想起了这一点。

无论何时使用或创建与自然语言处理相关的解决方案,都存在假阳性(FP)假阴性(FN)的可能性。FP 或 FN 是指自然语言处理算法错误地预测了输出。当输出返回为真,但实际结果是假时,这被认为是 FP。当模型返回为假或缺失,但正确的结果应该是返回真时,这被称为 FN。相反,当算法或模型正确预测输出时,这被称为真阳性(TP)真阴性(TN)。通常使用混淆矩阵来识别和训练模型,以减少 FP 和 FN。

我创建了一个类似于以下截图中的混淆矩阵版本,以帮助识别自然语言处理预测结果,其中阳性结果用绿色表示,阴性结果用红色表示。颜色和标识的确定是通过比较预测输出中的实际诗句来实现的。例如,如果使用计算机模型来预测测试某个人是否患有疾病的结果,一个TPTN将表明模型准确预测了该人患有疾病或确认他们没有疾病。对于这些情况,绿色方框表示与实际结果相匹配。对于任何其他结果,方框都涂成红色,因为预测不准确地返回了错误的结果:

图片

当数据量较小时,验证自然语言处理模型的结果可以通过人工进行,这可以确保算法背后的代码具有可信度。然而,当数据量较大时,这变成了一项耗时的工作,在许多情况下,没有替代解决方案(如众包)就无法实现验证。众包解决方案是指在数据录入过程中使用人类,但通过分解任务并将它们分配给大量人群来大规模验证数据。

典型的自然语言处理或数据科学模型将使用监督学习技术,其中使用训练数据集来教会模型在预测输出中保持准确。用于训练的标记数据将是一个由具有准确结果的人或人们分类的小数据集。因此,用于准确预测结果的统计模型是基于根据训练数据教导自然语言处理模型。在监督式自然语言处理模型中使用错误标记的训练数据会导致更多的假阳性和不准确的结果。如果你与使用监督学习的数据科学家或数据工程团队合作,你可以通过询问用于重新训练和评估模型准确性的过程来展示你的数据素养知识。

现在我们对处理非结构化数据有一些基本理解,让我们来看看如何从其来源收集它。对于来自 Twitter 等平台的社交媒体数据,使用它们的 API 连接将是一个选项,并允许您通过将数据流式传输到 Jupyter 笔记本中几乎实时地消费数据。使用任何社交媒体的 API 将需要您设置用户账户并创建一个密钥,这样您就可以通过 HTTP 使用 REST 客户端连接。

根据您工作站使用的网络连接,您可能需要调整防火墙或代理设置,以便允许您通过 API 和 REST 访问数据:

由于不同平台在导出社交媒体数据时存在限制和不同的 API 限制,我们将使用 Python 库中预安装的样本数据。自然语言工具包NLTK)包可以帮助我们处理多种用例中的非结构化数据。该软件是开源的,包含数十个模块,使用称为语料库的概念来支持 NLP。语料库创建了一个分类法,可以被语言学家、计算机科学家和机器学习爱好者使用。分类法是将文本体的语言分解为其基本元素的分解。例如,包括美国遗产词典和维基百科。它还作为使用软件时计数和搜索单词的基础。

语料库的实际应用

在我们的第一个例子中,我们将使用布朗语料库,它包含数百个按神秘短篇小说、政治新闻稿和宗教等主题的杂乱组合分类的样本语言文本。原始集合有超过一百万个单词被定义并标记为名词、动词和介词等词性。要在 Jupyter 笔记本中使用布朗语料库,请按照以下步骤操作:

  1. 启动一个新的 Jupyter 笔记本,并将其命名为ch_10_exercises

  2. 通过在 Jupyter 笔记本中添加以下命令并运行单元格来导入以下库。您可以自由地通过创建自己的笔记本来跟随(我在 GitHub 上放置了一个副本以供参考):

In[]: import nltk

该库应该已经通过 Anaconda 可用。有关设置环境的帮助,请参阅第二章,Python 和 Jupyter Notebook 安装概述。如果您在防火墙后面,有一个nltk.set_proxy选项可用。有关更多详细信息,请查看www.nltk.org/上的文档。

  1. 接下来,我们下载我们想要使用的特定语料库。或者,您可以使用all参数下载所有包:
In[]: nltk.download('brown')

输出将类似于以下截图,其中确认了包的下载,并且输出通过True进行了验证:

  1. 要确认包在你的 Jupyter 笔记本中可用,我们可以使用以下命令通过常用的别名brown引用语料库:
In[]: from nltk.corpus import brown
  1. 要显示布朗语料库中可用的一些单词的列表,请使用以下命令:
In[]: brown.words()

输出将类似于以下截图,其中显示六个单引号内的单词,以及方括号内的省略号:

图片

  1. 要计算所有可用的单词,我们可以使用len()函数,该函数计算字符串的长度或对象(如值数组)中的项目数量。由于我们的值由逗号分隔,它将计算所有可用的单词。为了使其格式化更容易,让我们将输出分配给一个名为count_of_words的变量,我们可以在下一步中使用它:
In[]: count_of_words = len(brown.words())
  1. 为了使数据消费者更容易理解输出,我们使用print()format()函数通过以下命令显示结果:
In[]: print('Count of all the words found the Brown Corpus =',format(count_of_words,',d'))

输出将类似于以下截图,其中将显示一个句子,该句子包括分配给count_of_words变量的所有单词的动态计数。我们还格式化了值以显示逗号:

图片

太棒了,你现在已经成功加载了你的第一个 NLP 库,并且能够对流行的语料库包运行一些命令。让我们继续通过解释为什么分词很重要来剖析 NLP 的不同元素。

分词解释

分词是将非结构化文本(如段落、句子或短语)分解成一系列称为标记的文本值的过程。标记是 NLP 函数使用的最低单位,用于帮助识别和处理数据。这个过程创建了一个自然层次结构,有助于从最高单位到最低单位识别关系。根据源数据,标记可以代表单词、句子或单个字符。

分词文本、句子或短语的过程通常从使用它们之间的空白字符将单词分开开始。然而,为了准确识别每个标记,需要库包考虑到诸如连字符、撇号和语言字典等例外情况,以确保值被正确识别。因此,分词需要知道文本的原始语言才能进行处理。例如,Google Translate 是一个可以识别语言的 NLP 解决方案,但用户仍然可以选择定义它,以确保翻译的准确性。

这就是为什么分词是一个不断发展的过程的原因之一。例如,随着新单词被添加到英语中,NLP 参考库需要更新。处理讽刺、双重含义和流行语可能会在使用 NLP 解决方案(如 Alexa 或 Siri)时在翻译中丢失。例如,短语社会大使对人类来说有明显的含义,但需要库被训练以识别它作为一个标记短语。

NLP 用于解决这个问题的技术有几个。第一个被称为 n-gram,这是将同一句子中的单词作为一组组合的过程,通常是两到三个单词,以创建一个可以被 NLP 库识别的模式。缩写也可以用于 n-gram 中,但需要识别或训练才能有效。n-gram 可以用来识别社会大使,以理解这两个值可以一起使用。

n-gram 的另一个常见参考是二元组,它只使用两个单词。n表示 gram 的数量,所以 uni-gram 代表一个 gram,bi-gram 代表两个,tri-gram 代表三个,依此类推。

另一个概念被称为词袋,这指的是在源数据中存在特定单词的高频出现。使用词袋是另一种有助于识别大型文本数据源中模式和关键术语搜索的有用方法。例如,一个用于提高系统故障响应时间的预测模型可以使用在帮助台票据中找到的历史文本日志。词袋技术可以用来创建多个标志字段(是或否)作为算法的输入。

因此,分词和 NLP 的研究是一个深奥的主题,它将保持为一个不断发展的科学。我建议继续深入研究这个主题。斯坦福大学 NLP 网站是进一步阅读部分中一个极好的信息来源。

让我们在 Jupyter 笔记本中探索 NLTK 库中可用的附加功能。另一个可下载的库被称为Punkt,它用于将句子分解成单词。我在进一步阅读部分中包含了一个链接,指向 NLTK 提供的下载。算法背后的代码需要大量的文本以便模型可以进行训练,但 NLTK 数据包包括一个预训练的英文选项,我们可以使用。

分词实战

你将继续通过回到 Jupyter 中的ch_10_exercises笔记本:

  1. 在你的 Jupyter 笔记本中添加以下命令并运行单元格以导入以下库。你可以自由地跟随创建自己的笔记本(我已经在 GitHub 上放置了一个副本以供参考):
In[]: nltk.download('punkt')

输出将类似于以下截图,其中确认了包的下载,并且输出通过True进行了验证:

图片

  1. 接下来,我们将创建一个名为input_sentence的新变量,并将其分配给一个必须用双引号括起来并单行输入的自由文本句子。在您运行单元格后,将不会有输入:
In[]: input_sentence = "Seth and Becca love to run down to the playground when the weather is nice."
  1. 接下来,我们将使用 NLTK 库中可用的word_tokenize()函数来分解单个单词和任何标点符号:
In[]: nltk.word_tokenize(input_sentence)

输出将类似于以下截图,其中单个单词从句子中分离出来,形成一个包含单引号和方括号的值数组:

截图

  1. 接下来,让我们通过句子进行分词,这需要您使用以下命令从 NLTK 的tokenize库中导入sent_tokenize选项:
In[]: from nltk.tokenize import sent_tokenize
  1. 现在,让我们将一个名为input_data的新变量分配给一组句子,我们可以在代码的后续部分中使用。在您运行单元格后,将不会有输入:
In[]: input_data = "Seth and Becca love the playground.  When it sunny, they head down there to play."
  1. 然后,我们将input_data变量作为参数传递给sent_tokenize()函数,该函数将查看文本字符串并将其分解成单个标记值。我们用print()函数包装输出,以便在笔记本中清晰地显示结果:
In[]: print(sent_tokenize(input_data))

输出将类似于以下截图,其中单个句子被分解为一个包含单引号和方括号的字符串值数组:

截图

因此,现在您可以看到,拥有这些 NLTK 库功能可以帮助您通过将语言分解成基础部分来处理非结构化数据。作为一名数据分析师,您将面临许多不同形式的自由文本,因此现在您有一些额外的资源可以利用。一旦数据被分词,将提供额外的选项,我们将在下一节中探讨,例如通过计数单词频率来识别底层源中的模式。

计数单词并探索结果

计算单词频率将提供关于非结构化源文本的初始元数据。在文本体中暴露单词或特定单词缺失的计数被称为文本挖掘。文本挖掘将提供关于数据的分析,以便数据分析师可以确定数据资产的价值以及如何使用它来回答业务问题。同样,您可以通过查看应用程序系统日志来识别在意外中断期间影响用户的关键词模式。一旦确定了这些单词或短语,您就可以与开发者合作来识别根本原因,并减少对应用程序用户的影响。

文本分析中一个流行的选项是使用正则表达式或简称regex。正则表达式的概念是当你使用一系列规则和搜索模式从非常大的非结构化文本中提取特征时。当逐行读取文本不合理,基于所需的时间和人数时,正则表达式变得有用。正则表达式有广泛的应用,包括如何从源文本中分离电子邮件、电话号码和标签。例如,如果你在处理帮助台票据日志,你会将正则表达式规则的成功匹配标记为具有唯一票据 ID,这样你就可以将非结构化数据重新连接到数据模型。

正则表达式涵盖了各种技术,包括通配符字符搜索、基于限定符的模式匹配以及用于识别文本数据开始或结束的锚点。正则表达式规则通常与标准软件工程相结合,因此代码在查看高频数据源(如聊天机器人或系统日志)时可以模块化和自动化。例如,如果你在客户服务系统中创建了一个针对关键词frustrated任何组合的正则表达式规则,你可以创建一个名为is_customer_frustrated_yes_no的标志字段,其值为 1 表示真,0 表示假。正则表达式规则可以并且应该随着时间的推移而演变,基于数据和验证,准确的规则非常重要。这可以通过对数据进行随机抽样、手动验证条件是否存在并返回正确结果来完成。

单词计数

在我们探索这些选项之前,让我们继续进行 Jupyter 笔记本练习,并了解如何从自由文本的总体中计算单词频率。

你将继续通过回到 Jupyter 中的ch_10_exercises笔记本:

  1. 导入 NTLK 库中可用的概率模块,以统计文本体中单词的频率。运行单元格后不会返回任何结果:
In[]: from nltk.probability import FreqDist
  1. 接下来,使用布朗语料库探索大量文本。为此,我们使用FreqDist()函数将所有可用的标记词的总体分配给一个名为input_data的变量。为了查看此数据的处理结果,我们可以打印变量:
In[]: input_data = FreqDist(brown.words())
print(input_data)

输出将类似于以下截图,其中使用print()函数针对分配的变量input_data计算并打印频率分布结果:

图片

  1. 要查看input_data中存在的最常见单词列表,我们可以使用most_common()函数并配合一个参数来控制显示的数量。在这种情况下,我们想查看前 10 个:
In[]: input_data.most_common(10)

输出将类似于以下截图,其中显示了一个包含十个行的两列表格,每个行显示一个标记,这可能是一个单词、标点符号或用单引号括起来的字符,以及一个整数值,表示该单词在源数据中出现的累积次数:

图片

通过这个练习,我们可以从非结构化文本中识别和提取单词,这是创建正则表达式规则的基础。下一节将专注于规范单词以提高 NLP 模型预测的准确性。

文本规范化技术

在大多数情况下,通过添加新的代码逻辑或库来使正则表达式规则更智能是必要的。这样做的一种方法是通过使用规范文本的概念,即词干提取和词形还原。这两个术语都根植于语言学的研究,并且由于将 NLP 解决方案集成到从客户服务到语音到文本功能等各个方面,它们在技术中的应用已经爆炸式增长。

当应用于 NLP 时,词干提取是指任何单词被程序性地识别为其常见的词根形式。在这个过程中,任何后缀、复数形式或同义词都会被识别。词干提取器需要一个参考字典或查找表才能准确,因此需要源语言。词形还原考虑了单词的所有变体,以便将其根植于词典源。根据我的研究,词干提取和词形还原在 NLP 中通常一起使用,你可以从使用开源库开始,这些库我在《进一步阅读》部分中提到了。这些库应该足以涵盖常见单词,但你的组织中的类比或自定义术语将需要一个新语料库。使用词干提取或词形还原的简单例子包括识别单词fishes出现时,返回fish,或者geese返回goose

这两个概念的主题相当广泛,所以我鼓励你继续学习它们,但底线是使用这些概念的好处将有助于清理和规范数据以进行分析。将多个相似值组合为单个值的数据规范化是必要的。这减少了你需要分析的数据量,并为创建数据科学或机器学习模型等更深入的统计分析做好了准备。

词干提取和词形还原的实际应用

对于我们的练习,我们将使用 Porter 词干提取器,它通常用于帮助准备文本数据和规范 NLP 中的数据。

让我们继续,回到 Jupyter 中的ch_10_exercises笔记本:

  1. 导入 NTLK 库中可用的PorterStemmer模块以规范一个单词。运行单元格后不会返回任何结果:
In[]: from nltk.stem import PorterStemmer
  1. 要导入此功能的实例以便在代码中稍后引用,我们使用以下代码。运行单元格后不会返回任何结果:
In[]: my_word_stemmer = PorterStemmer()
  1. 现在,你可以将单个单词传递到实例中,以查看该单词将被如何规范化:
In[]: my_word_stemmer.stem('fishing')

输出将类似于以下截图,其中单词 fishing 的词干将显示为 fish

图片

  1. 要使用词元特征,我们需要使用以下命令下载 WordNet 语料库:
In[]: nltk.download('wordnet')
  1. 要导入此功能的实例以便在代码中稍后引用,我们使用以下代码。运行单元格后不会返回任何结果:
In[]: from nltk.stem import WordNetLemmatizer
my_word_lemmatizer = WordNetLemmatizer()
  1. 为了查看我们之前用于词干化的相同单词的词元输出,我们将相同的单词传递到 lemmatize() 函数中:
In[]: my_word_lemmatizer.lemmatize('fishing')

输出将类似于以下截图,其中单词 fishing 的词元将显示为 fishing

图片

那么,为什么结果不同?NLTK 语料库中每个算法都采用不同的方法来规范化单词。词元化将调整到字典中定义的单词的形式或结构,而词干化旨在将单词分解为其词根。每个都是可用的工具,并且根据用例,您可能需要调整使用哪种方法来规范化分析数据。

为了更进一步,让我们使用循环将单词列表传递到每个实例中,并打印出结果以查看它们与原始单词的比较。我们将通过限制结果来使用布朗单词的样本:

如果你将超过一百万个单词传递到你的 Jupyter Notebook 会话的循环中,它将需要更长的时间来运行,并且需要占用资源(RAM 和 CPU)来处理。

  1. 要创建一个列表但限制单词仅限于样本,我们可以将其分配给一个变量,我们使用以下命令。运行单元格后不会返回任何结果:
In[]: my_list_of_words = brown.words()[:10]
  1. 现在,我们创建一个循环,针对列表中的每个值打印结果。我们包括一些格式化,以便更容易理解每行的结果。确保包括换行符以创建新行,以便在没有错误的情况下使用 print() 函数:
In[]: for x in my_list_of_words :    
    print('word =', x, ': stem =', my_word_stemmer.stem(x), ': lemma =', my_word_lemmatizer.lemmatize(x))

输出将类似于以下截图,其中打印了带有冒号分隔符的十行结果,用于分隔原始单词、单词的词干和单词的词元:

图片

现在我们已经学习了如何在非结构化数据中规范化单词,让我们了解如何从数据中排除单词或短语以减少噪声,这样我们就可以专注于可以提供洞察力的有价值的关键词。

排除分析中的单词

在数据分析中,通过视觉筛选数百万个单词是不切实际的,因为语言中包含许多在文本主体中重复的连接动词。当你在源数据上应用 NLP 并对其进行标准化后,像amisarewaswerebeingbeen这样的常见单词将出现在most_common()列表的顶部。在改进 NLP 库的过程中,创建了一个停用词字典,包括一个更全面的单词列表,这些单词在文本分析中提供的价值较小。例如,停用词包括连接动词以及像theanauntil这样的单词。目标是创建一个数据子集,在从标记值中过滤掉这些停用词后,你可以专注于分析。

自然语言处理(NLP)可能需要大量的 CPU 和 RAM 资源,尤其是在处理大量词汇集合时,因此你可能需要将你的数据分成逻辑块,例如按字母顺序,以完成你的分析。

你将继续回到 Jupyter 中的ch_10_exercises笔记本:

  1. 使用以下命令从 NLTK 库下载stopwords语料库:
In[]: nltk.download('stopwords')
  1. 接下来,导入停用词和word_tokenize功能,以便在练习中稍后使用:
In[]: from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
  1. 现在,让我们将一个名为input_data的新变量分配给一组句子,我们可以在稍后的代码中使用它。运行单元格后不会有输入:
In[]: input_data = “Seth and Becca love the playground.  When it's sunny, they head down there to play.”
  1. 我们将分配名为stop_wordsword_tokens的对象变量,以便在代码中稍后引用:
In[]: stop_words = set(stopwords.words('english'))word_tokens = word_tokenize(input_data)
  1. 最后,我们有几行代码将循环遍历input_data中的单词标记,并将它们与stop_words进行比较。如果它们匹配,将被排除。最终结果将打印出经过标记化并移除停用词后的原始input_data。在输入代码时,请确保使用正确的缩进:
In[]: input_data_cleaned = [x for x in word_tokens if not x in stop_words]
input_data_cleaned = []

for x in word_tokens:    
    if x not in stop_words:        
        input_data_cleaned.append(x)       
print(word_tokens)
print(input_data_cleaned)

输出将类似于以下截图,其中原始句子以包含所有单词的标记形式显示。输出的第二行将包含较少的标记单词,因为像the这样的停用词已经被移除:

图片

太好了,我们现在已经学会了如何排除常见单词,这从大量文本中移除了噪声。你的分析重点将放在关键词和短语上,以在文本中提供上下文,而无需阅读整个非结构化数据体。

摘要

恭喜你,你已经成功走过了自然语言处理NLP)的基础,以及处理非结构化数据时可用的重要功能。我们探讨了自然语言工具包NLTK)Python 库,它通过下载不同的语料库来分析大量文本,提供了许多处理自由文本的选项。我们学习了如何将原始文本分割成有意义的单元,称为标记(tokens),以便进行解释和精炼。我们了解了正则表达式和模式匹配在 NLP 中的应用。我们还探讨了如何使用概率和统计模块来计算文本集中单词的频率。接下来,我们学习了如何使用词干提取和词形还原函数来规范化单词,这展示了单词的变体如何影响你的数据分析。我们解释了 n-gram 的概念以及如何使用 stopwords 来移除在处理大量自由文本数据时常见的噪声。

在下一章,第十一章,《实用情感分析》,我们将展示如何将预测模型应用于非结构化数据。

进一步阅读

如需了解更多关于本章相关主题的信息,您可以参考以下链接:

实用情感分析

这将是一个有趣的章节。在本章中,我们将探索并演示一些使用自然语言处理NLP)概念的实际例子,以了解非结构化文本如何转化为洞察。在第十章“探索文本数据和非结构化数据”中,我们探讨了自然语言工具包NLTK)库以及与识别单词、短语和句子相关的一些基本功能。在这个过程中,我们学习了如何处理数据和分类文本,但并未超出这个范围。在本章中,我们将学习情感分析,它预测算法输入文本的潜在语气。在共同分析一个例子之前,我们将分解构成 NLP 模型的要素以及用于情感分析的包。

本章我们将涵盖以下主题:

  • 为什么情感分析很重要

  • NLP 模型的要素

  • 情感分析包

  • 情感分析实践

让我们开始吧。

第十五章:技术要求

你可以在此处找到本书的 GitHub 仓库:github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter11

你可以从以下链接下载并安装本章所需的软件:www.anaconda.com/products/individual

为什么情感分析很重要

现在,我们生活在一个数字时代,数据与我们的日常生活紧密相连。然而,由于大部分数据都是非结构化的,且数据量巨大,它需要统计库和机器学习ML)技术来解决技术问题。NLTK 库为我们提供了一个处理非结构化数据的框架,而情感分析则是 NLP 中的一个实际应用案例。情感分析,或称为意见挖掘,是一种监督式机器学习,需要训练数据集来准确预测输入的句子、短语、标题甚至推文是正面、负面还是中性。一旦模型被训练,你就可以像传递函数一样将非结构化数据输入其中,它将返回一个介于负一和正一之间的值。这个数值将输出小数,且越接近整数,模型的准确性就越高。情感分析是一个不断发展的科学,因此我们的重点将放在使用 NLTK 语料库库上。与任何 NLP 模型一样,如果你没有好的输入训练数据样本,你会在预测输出中找到不准确之处。

此外,请注意,NLP 和情感分析是一个深奥的主题,如果您计划使用内部公司数据源实现自己的模型,则应由数据科学家或机器学习工程团队进行验证。话虽如此,您今天会在许多不同的应用中注意到情感分析,本章中的练习为您提供了数据分析的另一个工具。了解如何使用情感分析的另一个好处是,它允许您就模型输出的数据进行辩论。能够捍卫处理非结构化数据的准确性和预测性将提高您的数据素养技能。例如,假设您正在分析一个关于餐厅的推文群体,该餐厅过去在营销活动中混合了正面和负面的评论。如果您的分析结果为 100%正面,您应该开始质疑训练数据、数据来源以及模型本身。当然,所有推文都是正面的可能性是存在的,尤其是在数据量较小的情况下,但每个推文都有正面情感的可能性大吗?

正如第一章“数据分析基础”中所述,无论使用什么技术和工具进行分析,了解您的数据(KYD)仍然很重要。然而,今天为什么情感分析很重要需要说明。首先,模型的准确性显著提高,因为训练数据越多,预测输出的效果越好。第二点是,NLP 模型可以扩展到人类在相同时间内无法处理的内容。最后,今天可用的情感分析替代方案,如专家系统,由于实施它们所需的时间和资源,成本更高。基于文本逻辑和通配符关键字搜索的专家系统开发是僵化的,且难以维护。

现在,让我们来探讨构成自然语言处理(NLP)元素的内容以及它在情感分析中应用的过程。

NLP 模型的元素

为了总结使用 NLP 监督机器学习模型进行情感分析所需的过程,我创建了一个以下图表,它显示了从字母AE的逻辑进展:

图片

这个过程从我们的源非结构化输入数据开始,这在前面图中用字母 A 表示。由于非结构化数据有不同的格式、结构和形式,如推文、句子或段落,我们需要执行额外的步骤来处理数据以获得任何见解。

下一个元素被命名为文本归一化,并在前面的图中用字母 B 表示,涉及诸如分词、n-gram 和词袋模型BoW)等概念,这些概念在第十章“探索文本数据和非结构化数据”中介绍过。让我们更详细地探讨它们,以便我们可以了解它们在情感分析中的应用。BoW 是指将文本字符串(如句子或段落)分解以确定单词出现的次数。在创建词袋表示的过程中进行分词,单词在句子、推文或段落中的位置变得不那么相关。每个单词如何被分类、分类和定义,将作为下一个过程的输入。

将标记和词袋视为情感分析食谱的原始原料;就像烹饪一样,原料需要额外的步骤进行精炼。因此,分类的概念变得很重要。这被认为是特征,并在前面的图中用字母 C 表示。因为对计算机来说,标记不过是一串 ASCII 字符,所以词嵌入和标记是将其转换为机器学习模型输入的过程。一个例子是将每个单词分类为一个值对,如一或零,以表示真或假。此过程还包括寻找相似单词或分组,以便解释上下文。

创建特征被称为特征工程,这是监督式机器学习的基础。特征工程是将非结构化数据元素转换为预测模型特定输入的过程。模型是抽象的,其输出的准确性仅取决于其背后的输入数据。这意味着模型需要带有提取特征的训练数据来提高其准确性。没有特征工程,模型的输出结果将是随机猜测。

创建预测输出

为了了解如何从非结构化数据中提取特征,让我们通过 NLTK 性别特征来举例,该特征对原始示例进行了一些小的修改。您可以在“进一步阅读”部分找到原始来源。

启动一个新的 Jupyter Notebook,并将其命名为 ch_11_exercises。现在,按照以下步骤操作:

  1. 通过在 Jupyter Notebook 中添加以下命令并运行单元格来导入以下库。您可以自由地跟随操作,创建自己的 Notebook。我已经在这个书的 GitHub 仓库中放置了一个副本供参考:
In[]: import nltk

该库应该已经通过 Anaconda 可用。有关设置环境的帮助,请参阅第二章“Python 概述和安装 Jupyter Notebook”。

  1. 接下来,我们需要下载我们想要使用的特定语料库。或者,您可以使用all参数下载所有包。如果您在防火墙后面,有一个nltk.set_proxy选项可用。有关更多详细信息,请查看nltk.org上的文档:
In[]: nltk.download("names")

输出将如下所示,其中确认了包的下载,并且输出被验证为True

  1. 我们可以使用以下命令来引用语料库:
In[]: from nltk.corpus import names
  1. 为了探索这个语料库中可用的数据,让我们对两个输入源male.txtfemale.txt运行print命令:
In[]: print("Count of Words in male.txt:", len(names.words('male.txt')))
print("Count of Words in female.txt:", len(names.words('female.txt')))

输出将如下所示,其中在笔记本中打印出每个源文件中找到的单词数量:

由于我们计算了每个源文件中找到的单词数量,我们现在对数据的大小有了更好的理解。让我们继续查看每个源的内容,查看每个性别文件的一些样本。

  1. 为了查看每个源中找到的前几个单词的列表,让我们对两个输入源male.txtfemale.txt运行print命令:
In[]: print("Sample list Male names:", names.words('male.txt')[0:5])
print("Sample list Female names:", names.words('female.txt')[0:5])

输出将如下所示,其中在笔记本中打印出每个源文件中找到的单词列表:

记住,计算机并不知道一个名字实际上返回的是malefemale的值。语料库将它们定义为两个不同的源文件,作为 NLTK 库识别为单词的值列表,因为它们被定义为这样的。有成千上万的名称被定义为男性或女性,你可以使用这些数据作为情感分析输入。然而,仅识别性别并不能确定情感是积极的还是消极的,因此还需要额外的元素。

在第一个图中的下一个元素,标记为 D,是实际的 NLP 监督机器学习 算法。记住,构建一个准确模型需要使用特征工程,以及 NLTK 库和分类器模型。当正确使用时,输出将基于输入的 训练测试 数据。模型应该始终进行验证,并且应该测量准确性。在我们的例子中,即构建一个基本的性别判断模型,我们将使用 NLTK 库中可用的 NaiveBayesClassifier。朴素贝叶斯分类器是一个基于贝叶斯定理创建的机器学习模型,用于根据另一个类似事件发生的频率来确定事件发生的概率。分类器是一个过程,它根据输入的特征数据集选择正确的标签值或标签。这些模型和库背后的数学概念非常广泛,因此我在 进一步阅读 部分添加了一些链接以供参考。为了完成第一个图中总结的情感分析元素,我们将创建一个预测输出,所以让我们继续在我们的 Jupyter Notebook 会话中继续:

  1. 创建一个名为 gender_features 的函数,该函数返回任何输入单词的最后一个字母。模型将使用这个分类器特征作为输入来预测输出,基于这样的概念:以字母 AEI 结尾的名字更有可能是女性,而以 KORST 结尾的名字更有可能是男性。运行单元格后不会有输出:
In[]: def gender_features(word):       
    return {'last_letter': word[-1]}

记住,在您的单元格中缩进第二行,以便 Python 可以处理该函数。

  1. 为了确认函数将返回一个值,输入以下命令,该命令打印任何输入名称或单词的最后一个字符:
In[]: gender_features('Debra')

输出将如下所示,其中在 Notebook 中打印了输入单词 Debra 的最后一个字符,并带有 Out[]

图片

  1. 创建一个名为 labeled_names 的新变量,该变量遍历两个源性别文件,并为每个 名称-值对 分配标签,以便它可以被识别为男性或女性,然后输入到模型中。为了在循环完成后查看结果,我们打印前几个值以验证 labeled_names 变量是否包含数据:
In[]: labeled_names = ([(name, 'male') for name in names.words('male.txt')] + [(name, 'female') for name in names.words('female.txt')])
print(labeled_names[0:5])

输出将如下所示,其中每个来自源文件的名称值将与 malefemale 标签相结合,具体取决于它来自哪个文本文件源:

图片

  1. 由于模型应该使用随机值列表进行训练以避免任何偏差,我们将输入随机函数并打乱所有名称和性别组合,这将改变它们在 labeled_names 变量中的存储顺序。我添加了一个 print() 语句,以便您可以看到与先前步骤中创建的输出之间的差异:
In[]: import random
random.shuffle(labeled_names)
print(labeled_names[0:5])

输出将如下所示,其中源文件中的每个名字值将与来自哪个文本文件源的malefemale标签相结合:

注意,因为使用了random()函数,所以print()函数的结果每次运行单元格时都会改变。

  1. 接下来,我们将通过使用labeled_names变量中每个名字的最后一个字母为每个性别创建特征来训练模型。我们将打印出新的变量featuresets,以便您可以看到在下一步中如何使用这个特征:
In[]: featuresets = [(gender_features(n), gender) for (n, gender) in labeled_names]
print(featuresets[0:5])

输出将如下所示,其中每个名字最后一个字母的组合被分配给一个性别值,从而创建了一个名字-值对的列表:

  1. 接下来,我们将从featuresets变量列表中切割数据,形成两个输入数据集,分别称为train_settest_set。一旦我们将这些数据集分开,我们就可以使用train_set作为分类器的输入。我们使用len()函数来给我们一个每个数据集大小的感觉:
In[]: train_set, test_set = featuresets[500:], featuresets[:500]
print("Count of features in Training Set:", len(train_set))
print("Count of features in Test Set:", len(test_set))

输出将如下所示,其中len()函数的结果提供了每个数据集相对于其他数据集大小的上下文:

  1. 现在,我们将train_set变量作为输入传递给 NLTK 朴素贝叶斯分类器。模型被命名为classifier,因此您可以在下一步中像调用函数一样调用它。运行单元格后不会有输出:
In[]: classifier = nltk.NaiveBayesClassifier.train(train_set)
  1. 现在,我们将通过以下命令将随机名字发送到模型中,以验证模型的结果:
In[]: classifier.classify(gender_features('Aaron'))
classifier.classify(gender_features('Marc'))
classifier.classify(gender_features('Debra'))
classifier.classify(gender_features('Deb'))
classifier.classify(gender_features('Seth'))

输出将如下所示,其中每个名字在通过classifier模型作为参数传递后,都会显示malefemale的性别值:

恭喜您——您已成功创建了您的第一个监督式机器学习模型!如您所见,分类器模型存在一些准确度问题,在某些情况下返回了错误值。例如,当您传入AaronMarcDebra的值时,性别预测结果是正确的。Aaron这个名字在训练数据中出现过,所以这并不令人惊讶。然而,模型显示出不完整或需要额外特征的迹象,因为它在用昵称Deb代表Debra以及代表男性名字Seth时返回了错误的性别。

我们如何解决这个问题?我们可以使用几种方法,我们将在下面一一探讨。

情感分析包

NLTK 库包含一些包来帮助我们解决我们在性别分类器模型中遇到的问题。第一个是 SentimentAnalyzer 模块,它允许您使用内置函数添加额外的功能。这些包的特殊之处在于,它们超越了传统的函数,其中定义的参数被传递进来。在 Python 中,参数(args)和关键字参数(kwargs)允许我们将名称-值对和多个参数值传递给一个函数。这些用星号表示;例如,*args**kwargs。NLTK 的 SentimentAnalyzer 模块是一个用于教学目的的有用工具,因此让我们继续通过浏览其中可用的功能来继续。

第二个是称为 VADER 的,代表 Valence Aware Dictionary and Sentiment Reasoner。它是为了处理社交媒体数据而构建的。VADER 情感库有一个称为 lexicon 的字典,并包括一个基于规则的算法,专门用于处理缩写、表情符号和俚语。VADER 的一个不错的特点是它已经包含了训练数据,我们可以使用一个名为 polarity_scores() 的内置函数,该函数返回输出中显示的关键见解。第一个是介于负一和正一之间的复合得分。这为您提供了一个单一得分中 VADER 字典评级的标准化总和。例如,如果输出返回 0.703,这将是一个非常积极的句子,而复合得分为 -0.5719 将被解释为消极。VADER 工具的下一个输出是关于输入是积极、消极还是中性的分布得分,范围从零到一。

例如,句子 我恨我的学校! 会返回以下截图所示的结果:

图片

如您所见,返回了一个 -0.6932 的复合值,这验证了 VADER 模型准确预测了非常消极的情感。在同一输出行上,您可以看到 'neg''neu''pos',分别代表消极、中性和积极。每个值旁边的指标提供了一些关于复合得分是如何得出的更多细节。在前面的截图中,我们可以看到一个 0.703 的值,这意味着模型预测的消极程度为 70.3%,剩余的 29.7% 为中性。模型在 pos 旁边返回了一个 0.0 的值,因此基于内置的 VADER 训练数据集,没有积极的情感。

注意,VADER 情感分析评分方法已经训练过,可以处理社交媒体数据和非正式的正式语法。例如,如果一条推文包含多个感叹号以强调,复合评分将增加。大写字母、连词的使用以及脏话的使用都将计入模型输出的结果中。因此,使用 VADER 的主要好处是它已经包含了那些用于特征和训练模型的额外步骤,但你失去了使用额外功能来自定义它的能力。

现在我们对 VADER 工具有了更好的理解,让我们通过一个使用它的例子来演示。

情感分析实战

让我们继续我们的 Jupyter Notebook 会话,并介绍如何安装和使用 VADER 情感分析库。首先,我们将通过一个手动输入的例子来演示,然后学习如何从文件中加载数据。

手动输入

按照以下步骤学习如何在 VADER 中使用手动输入:

  1. 导入 NLTK 库并下载vader_lexicon库,以便所有必要的函数和功能都将可用:
In[]: import nltk
nltk.download('vader_lexicon')

输出将如下所示,其中包下载将被确认,输出被验证为True

图片

  1. 从 NLTK Vader 库导入SentimentIntensityAnalyzer。运行单元格时不会有输出:
In[]:from nltk.sentiment.vader import SentimentIntensityAnalyzer
  1. 为了简化,我们将分配一个名为my_analyzer的变量对象,并将其分配给SentimentIntensityAnalyzer()模型。运行单元格后不会有输出:
In[]:my_analyzer = SentimentIntensityAnalyzer()
  1. 接下来,我们将创建一个名为my_input_sentence的变量,并将其分配一个字符串值I HATE my school!。在第二行,我们将调用模型并将变量作为参数传递给polarity_scores()函数:
In[]:my_input_sentence = "I HATE my school!"
my_analyzer.polarity_scores(my_input_sentence)

输出将如下所示,其中我们可以看到 VADER 情感分析模型的结果:

图片

极好——你现在已经使用了 VADER 情感分析模型,并返回结果以确定句子是正面还是负面。现在我们了解了模型如何处理单个输入句子,让我们演示如何处理一个示例社交媒体文件,并将其与我们使用pandasmatplotlib库所学的内容结合起来。

在下一个练习中,我们将处理一个文本文件源,你需要将其导入到你的 Jupyter Notebook 中。这是一个包含示例社交媒体类型自由文本的小型样本 CSV 文件,包括一个标签、非正式语法和额外的标点符号。

它有 2 列和 10 行内容,有一行标题行以便于参考,如下面的截图所示:

图片

社交媒体文件输入

让我们继续使用我们的 Jupyter Notebook 会话,并介绍如何处理这个源文件,以便它包含 VADER 情感分析,然后分析结果:

  1. 我们将导入一些额外的库,以便我们可以处理和分析结果,如下所示:
In[]:import pandas as pd
import numpy as np
%matplotlib inline
  1. 我们还必须安装一个名为 twython 的新库。使用以下命令在您的 Notebook 会话中安装它。twython 库包括一些功能,可以使其更容易读取社交媒体数据:
In[]:!pip install twython

输出将如下所示,其中将显示结果安装情况。如果您需要升级 pip,可能需要运行额外的命令:

  1. 如果需要,重新导入 NLTK 库并导入 SentimentIntensityAnalyzer 模块。运行单元格后不会显示任何输出:
In[]:import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
  1. 定义一个名为 analyzer 的变量,以便在代码中更容易引用。运行单元格后不会显示任何输出:
In[]:analyzer = SentimentIntensityAnalyzer()
  1. 如果需要,重新下载 NLTK 的 vader_lexicon
In[]:nltk.download('vader_lexicon')

输出将如下所示,其中将显示下载结果:

  1. 现在,我们将使用 pandas 库读取 .csv 文件,并将结果分配给名为 sentences 的变量。为了验证结果,您可以运行 len() 函数:
In[]:sentences = pd.read_csv('social_media_sample_file.csv')
len(sentences)

确保将源 CSV 文件上传到正确的文件位置,以便您可以在 Jupyter Notebook 中引用它。

输出将如下所示,其中将显示 10 的值。这与源 CSV 文件中的记录数相匹配:

  1. 为了预览数据并验证您的 DataFrame 是否正确加载,您可以运行 head() 命令:
In[]:sentences.head()

输出将如下所示,其中将显示 head() 函数的结果,以验证源文件现在是一个 DataFrame:

  1. 以下代码块包括几个步骤,这些步骤将遍历 DataFrame,分析文本源,应用 VADER 情感度量,并将结果分配给 numpy 数组以方便使用。运行单元格后不会显示任何输出:
In[]:i=0 #reset counter for loop

#initialize variables
my_vader_score_compound = [ ] 
my_vader_score_positive = [ ] 
my_vader_score_negative = [ ] 
my_vader_score_neutral = [ ] 

while (i<len(sentences)):

    my_analyzer = analyzer.polarity_scores(sentences.iloc[i]['text'])
    my_vader_score_compound.append(my_analyzer['compound'])
    my_vader_score_positive.append(my_analyzer['pos'])
    my_vader_score_negative.append(my_analyzer['neg']) 
    my_vader_score_neutral.append(my_analyzer['neu']) 

    i = i+1

#converting sentiment values to numpy for easier usage
my_vader_score_compound = np.array(my_vader_score_compound)
my_vader_score_positive = np.array(my_vader_score_positive)
my_vader_score_negative = np.array(my_vader_score_negative)
my_vader_score_neutral = np.array(my_vader_score_neutral)

在 Jupyter Notebook 输入单元格中输入多个命令时,务必仔细检查缩进。

  1. 现在,我们可以扩展源 DataFrame,使其包括 VADER 情感模型的结果。这将创建四个新列。运行单元格后不会显示任何输出:
In[]:sentences['my VADER Score'] = my_vader_score_compound
sentences['my VADER score - positive'] = my_vader_score_positive
sentences['my VADER score - negative'] = my_vader_score_negative
sentences['my VADER score - neutral'] = my_vader_score_neutral
  1. 要查看更改,再次运行 head() 函数:
In[]:sentences.head(10)

输出将如下所示,其中将显示 head() 函数的结果,以验证 DataFrame 现在包括从上一步的循环中创建的新列:

  1. 虽然这些信息很有用,但仍需要用户逐行扫描结果。让我们通过创建一个新列来对化合物得分结果进行分类,使其更容易分析和总结结果。运行单元格后不会显示任何输出:
In[]:i=0 #reset counter for loop

#initialize variables
my_prediction = [ ] 

while (i<len(sentences)):
    if ((sentences.iloc[i]['my VADER Score'] >= 0.3)):
        my_prediction.append('positive')
    elif ((sentences.iloc[i]['my VADER Score'] >= 0) & (sentences.iloc[i]['my VADER Score'] < 0.3)):
        my_prediction.append('neutral')
    elif ((sentences.iloc[i]['my VADER Score'] < 0)):
        my_prediction.append('negative') 

    i = i+1
  1. 与之前类似,我们将取结果并添加一个名为my prediction sentiment的新列到我们的 DataFrame 中。在运行单元格后不会显示任何输出:
In[]:sentences['my predicted sentiment'] = my_prediction
  1. 要查看更改,请再次运行head()函数:
In[]:sentences.head(10)

输出将如下所示,其中将显示head()函数的结果,以验证 DataFrame 现在包括从上一步骤中循环创建的新列:

图片

  1. 为了更容易地解释结果,让我们通过使用聚合groupby来总结结果,在 DataFrame 上创建数据可视化。我们将使用matplotlib库中的plot()函数来显示水平条形图:
In[]:sentences.groupby('my predicted sentiment').size().plot(kind='barh');

输出将如下所示,其中将显示一个水平条形图,展示文本按情感(正面、负面和中性)的计数总结:

图片

如您所见,我们数据源中正面意见较多。这样解释结果要快得多,因为我们通过可视化结果使其更容易视觉上消费。我们现在有一个可重用的工作流程,通过查看源数据文件并应用 VADER 情感分析模型到每条记录来分析大量非结构化数据。如果您用任何社交媒体源替换样本 CSV 文件,您可以重新运行相同的步骤,看看分析如何变化。

VADER 模型的准确率分数约为 96%,根据相关研究,这已被证明比人类解释更准确。

由于代码中可以调整正面负面中性的区间,因此在分析中存在一些偏差。作为一名优秀的数据分析师,理解偏差可以帮助您根据特定需求调整它,或者能够传达处理自由文本数据时的挑战。

摘要

恭喜您——您已经成功走过了 NLP 的基础,应该对使用 NLTK 库进行监督机器学习有一个高级理解!情感分析是一个令人着迷且不断发展的科学,有许多不同的组成部分。我希望这个介绍是您继续研究的好开始,以便您可以在数据分析中使用它。在本章中,我们学习了情感分析的各个方面,例如特征工程,以及 NLP 机器学习算法的工作过程。我们还学习了如何在 Jupyter 中安装 NLP 库以处理非结构化数据,以及如何分析分类模型创建的结果。有了这些知识,我们通过一个示例了解了如何使用 VADER 情感分析模型,并可视化结果以进行分析。

在我们上一章第十二章“整合一切”中,我们将结合本书中涵盖的所有概念,并探讨一些真实世界的示例。

进一步阅读

整合一切

欢迎来到本章,我们将通过一些示例来展示你在这本书中学到的技能如何应用。在本章中,我们将学习关于开源真实世界数据集的知识,一些关于如何报告结果的小贴士,以及一个融合、转换和可视化来自多个数据源的数据的综合性项目。数据分析是一门技艺,也是一段旅程,其回报在于你永远不会停止学习新的数据处理方式、提供见解和解答问题的方法。数据素养技能,包括阅读、使用、分析和用数据辩论,与任何技术无关,但根据我的经验,没有任何经验能替代深入使用特定技术的体验。本书中我们选择使用的工具是 Jupyter Notebook,以及在使用 Python 生态系统时可用的一些可扩展库,如 pandas、NumPy 和 NTLK。随着你继续练习和应用这些技能,你将变成一个可以个人和职业上使用数据解决问题的通用资产。

在本章中,我们将涵盖以下主题:

  • 发现真实世界数据集

  • 报告结果

  • 综合性项目

让我们开始吧!

第十六章:技术要求

本书对应的 GitHub 仓库地址为github.com/PacktPublishing/Practical-Data-Analysis-using-Jupyter-Notebook/tree/master/Chapter12

此外,你可以从以下地址下载和安装本章所需的软件:www.anaconda.com/products/individual

发现真实世界数据集

在本书的整个过程中,我强调了分析的力量来自于将来自多个来源的数据融合在一起。单个数据源很少包含回答关键问题所需的所有字段。例如,如果你有一个时间戳字段但没有关于用户的地理位置字段,你就无法回答任何关于事件发生地点的数据相关的问题。

作为一名优秀的数据分析师,始终提供富有创造性的解决方案,这些解决方案填补了数据空白或通过包含外部数据源提供不同的视角。今天发现新的数据源比以往任何时候都要容易。让我们来看几个例子。

Data.gov

Data.gov 由美国总务管理局管理,提供数十万个关于各州和联邦层面各种主题的数据集。大多数数据集由特定机构整理并公开发布。这些数据集是开源的,但有限制。我喜欢使用 data.gov 的原因是其目录,它允许您跨所有主题、标签、格式和组织进行搜索。该网站使用开源技术创建,包括 CKAN.org,代表综合知识档案网络。这是一个专门为组织构建的开放数据门户平台,用于托管数据集并使其透明化。这个过程使数据集民主化,并为出版商制定了标准,例如公开数据格式(例如 CSV、API 和 XML)的源以及提供有关数据更新频率的详细信息。

以下是从 data.gov 网站截取的屏幕截图,您可以在该网站上搜索来自美国政府的开源数据集:

图片

DATA.GOV(data.gov)网站是一个良好的起点,但寻找特定的数据元素可能会感到不知所措。我发现下一个例子更容易且更快地找到和使用数据集。

人道主义数据交换

人道主义数据交换HDX)由于 COVID-19 大流行而成为热门话题,但多年来一直在赞助开源数据集。这些数据集包含来自世界各地的健康特定统计数据,重点是帮助人类。这是一个通常被称为数据善行的真正例子,因为该网站免费提供其对人们影响的透明度。我喜欢这个网站的方式,它将 Creative Commons 许可证集成到数据目录中,这样您就可以了解关于从源头重新使用或分发数据的任何限制。他们的服务条款的一部分是限制任何个人身份信息PII)的使用,这样数据就已经符合支持保护个人免受直接识别的法规。

以下是从 The Humanitarian Data Exchange(人道主义数据交换)网站(data.humdata.org)截取的屏幕截图,您可以在该网站上搜索关于世界各地位置的援助数据集:

图片

如果您正在寻找按全球统计指标分类的财务数据元素,您可以从世界银行数据门户开始搜索。

世界银行

世界银行 拥有一个开放的数据仓库,其中包含数千个数据集,这些数据集按国家分类并符合指标。该网站允许您将您的数据与全球基准指标进行比较,例如国内生产总值GDP),这为您分析创建阈值和性能指标。我发现该网站易于导航,并且可以快速识别可以轻松与其他数据集连接的数据集,因为它包括定义的数据类型值,如 ISO 国家代码。

以下是从世界银行开放数据网站(data.worldbank.org)的截图,您可以在其中搜索影响全球各国的金融数据集:

世界银行数据门户有许多丰富的示例,包括在使用它们之前进行快速分析和预览的数据可视化。我们将要查看的下一个网站,我们的全球数据,具有类似的可用性功能。

我们的全球数据

我们的全球数据网站始于牛津大学的研究数据,但已发展成为一个基于科学研究的在线出版物,旨在帮助世界使用数据解决问题。我喜欢这个网站,因为它可以揭示历史趋势,为人类在许多情况下如何改善提供背景信息,但当你查看不同国家时,改善的速度并不相同。我还发现他们的数据可视化易于使用和导航;例如,您可以在不同国家或地区之间过滤和比较结果。他们的数据和网站在 COVID-19 大流行期间变得非常有价值,因为您可以跟踪病例并比较不同国家以及美国国内的进展。

以下是从我们的全球数据网站(ourworldindata.org)的截图,您可以在其中探索数千个图表和开源数据,这些数据专注于帮助解决全球问题:

仅凭这里展示的少数例子,你可以看到你有权访问数千个可用于研究、混合或学习的数据集。请注意可能限制分发或限制你提取数据频率的开源许可证。当构建任何自动化数据管道或使用 API 按需提取数据时,这一点变得很重要。在这种情况下,你可能不得不寻找替代的付费数据公司。在任何情况下,即使源数据是符合和结构化的,也可能无法回答你分析所需的所有问题。另一个需要注意的点是数据的聚合级别可能不是很高。例如,如果数据按国家聚合,你就不能按城市合并数据。在这些情况下,在如何使用外部数据方面保持透明度很重要,同时引用来源并提供声明,说明正在使用外部数据。

接下来,我们将介绍一些可以用来报告分析结果的最佳实践。

报告结果

如何展示你的分析结果将根据受众、可用时间和所需详细程度来决定,以便讲述数据的故事。你的数据可能存在固有的偏差、不完整,或者需要更多属性来构建完整的画面,因此不要害怕在分析中包含这些信息。例如,如果你对气候变化进行了研究,这是一个非常广泛的话题,向你的分析消费者展示针对你数据集的具体假设的狭窄范围是很重要的。你如何以及在哪里包含这些信息并不像确保它们可供同行评审那样重要。

讲故事

使用数据讲故事需要一些练习,你需要时间来向受众传达你的信息。就像任何好的故事一样,以有开始、中间和结束的节奏展示数据结果将有助于分析内容的流畅性。我还发现使用类比来比较你的发现将在数据与预期消费者之间建立一些联系。了解你向谁展示分析结果与理解数据本身一样重要。

例如,在扑克牌游戏中,了解你的手牌概率和银行存款并不是成功的唯一因素。你对手的构成是影响你赢或输多少的一个因素。因此,了解桌面上玩家的反应将帮助你做出在游戏中弃牌、跟注或加注的决定。

因此,在展示结果时,要像扑克玩家一样思考,并注意你向谁展示数据以便说服观众接受你的结论。例如,如果你向高级管理层展示,时间有限,因此建议直接、简洁,并跳到总结结果。如果观众是你的同侪,那么讲述你得出结论的过程将产生共鸣,因为它建立了信誉和信任。

无论受众如何,如果你展示的图表随着时间的推移而趋势上升,请准备好提供证明基础来源的证据,以及如何计算该指标。如果没有这些信息,对重现你发现能力的怀疑将导致你的分析被驳回。

一个优秀的数据分析师将能够读懂房间,并了解在展示结果时需要多少细节。有很多次,我在展示发现时不得不缩短我的信息,因为当我抬头看人们参与度如何时,我意识到他们已经迷失了。与其继续下去,我停下来提出问题,这样我可以提供更多的清晰度。仅仅改变格式,在演示过程中提供提问时间,就帮助了观众和我重新将注意力集中在结论上。

因此,在分析中保持真实并提供透明度。如果你犯了错误或误解了数据,只要你继续改进并避免重复错误步骤,观众就会宽恕你。我发现,在展示自己之前,有能够提供诚实和建设性反馈的同侪、导师和经理,有助于改进你的信息和数据成果的展示。

数据素养的角度来看,关注点应放在分析中获得的认识上,而不仅仅是所使用的技术。意识到解读你结果的人将来自不同的视角。CEO 可以理解公司财务数据的资产负债表图表,但可能并不关心你分析中使用了哪个 NumPy 库。在我们这本书的最后一项练习中,我们将创建一个以使用来自多个来源的数据来回答现实世界问题为重点的毕业设计项目。

毕业设计项目

对于我们的现实世界数据集示例,我们将使用两个不同的来源,并使用我们在本书中学到的技术将它们结合起来。由于了解你的数据KYD)仍然适用,让我们来了解一下这些来源。

KYD 来源

第一个来源来自世界银行,是一份绿色债券列表,这些债券用于资助减少碳排放和与气候相关项目。它从网站上下载的,因此是一个基于存储为 CSV 文件的时间点的快照,包含 115 行和 10 列,包括标题。

在以下屏幕截图中可以看到 Microsoft Excel 中数据的视觉预览:

图片

源数据中包含一些我们可以通过直接分析获得的见解,例如以下内容:

  • 按货币发行的债券数量是多少?

  • 债券的货币分布总情况是怎样的?

  • 哪些债券将在未来 3 年、5 年、7 年或 10 年到期?

然而,对于发行国与当地货币相关的问题,我们存在数据缺口。由于货币汇率每日波动,如果我们有可用于通过货币字段加入的信息,我们就能回答更多的问题。因此,我们想要处理的第二个数据来源是来自人道数据交换HDX)网站的数据。这包括按日期从1/4/19995/7/2020,以 M/D/YYYY 日期格式指定的货币作为基准的外汇FX)汇率。这是一个可以下载的 CSV 文件,包含 5,465 行和 34 列,在特定日期上。文件中有一行标题,数据的第一条记录包括以井号(#)为前缀的元数据标签,这些标签由 HDX 网站用于元数据管理和编目。

以下截图显示了在 Microsoft Excel 中的数据视觉预览:

在使用 Jupyter Notebook 处理数据之前,如果希望直观地了解数据的结构,在 Microsoft Excel 或任何电子表格工具中预览数据是一种最佳实践。如果数据量很大,可以提取样本以便在工作站上加载。

因此,我们的债券数据按行列出值,我们的 FX 汇率数据则是按列列出每种货币的值,并为每个特定日期分配一个值。有几种方法可以解决这个问题,但以我们的示例为例,我们感兴趣的是将最新的 FX 汇率按货币与我们的债券数据混合,以便将美元等值转换为当地货币等值。这样,我们就可以在美元或相关国家的货币中进行数据分析,并报告结果。

练习

让我们打开一个新的 Jupyter Notebook 会话并开始:

  1. 我们将导入所需的库以处理和分析结果,包括以下命令:
In[]:import pandas as pd
import numpy as np
%matplotlib inline
  1. 我们将使用pandas库读取第一个.csv文件,并将结果分配给名为df_greenbonds的变量:
In[]:df_greenbonds = pd.read_csv('Green_Bonds_since_2008.csv')

确保将源 CSV 文件上传到正确的文件位置,以便在 Jupyter Notebook 中引用它。

  1. 为了验证所有记录都已成功加载,我们需要在 DataFrame 上运行一个shape()函数:
In[]:df_greenbonds.shape

输出将如下所示,其中将显示值11510。这些值与源 CSV 文件中的行数和列数相匹配:

  1. 要预览 DataFrame,我们可以运行以下head()函数:
In[]:df_greenbonds.head()

输出将如下所示,其中 DataFrame 的结果将在 Notebook 中显示:

图片

  1. 我们将使用 pandas 库读取第二个 CSV 文件,并将结果分配给名为 df_fx_rates 的变量:
In[]:df_fx_rates = pd.read_csv('ECB_FX_USD-quote.csv')

确保将源 CSV 文件上传到正确的文件位置,以便你可以在 Jupyter Notebook 中引用它。

  1. 为了验证所有记录都已成功加载,请在 DataFrame 上运行一个 shape() 函数:
In[]:df_fx_rates.shape

输出将如下所示,其中值 546434 将显示在括号中。这些值与源 CSV 文件中的行数和列数相匹配:

图片

  1. 为了预览 DataFrame,我们可以运行以下 head() 函数:
In[]:df_fx_rates.head()

输出将如下所示,其中 DataFrame 结果将在 Notebook 中显示:

图片

  1. 由于我们知道从前面的章节中数据本身是杂乱的,需要一些清理,我们将删除第一行,因为它包含 HDX 标签元数据值,这些值对于我们的分析不是必需的:
In[]:df_fx_rates = df_fx_rates.drop(0)
df_fx_rates.head()

建议你在分析步骤中逐步清理,以防需要调试并重新创建数据分析工作流程中的任何先前步骤。

输出将如下所示,其中 DataFrame 结果将在 Notebook 中显示:

图片

  1. 对于我们的分析,我们希望关注文件中可用的最新 FX 汇率。你可以取 DataFrame 中的第一行,但更稳健的方法是使用 max() 函数,这样数据的排序方式就无关紧要了。在过滤 DataFrame 之前,为了验证正确的值将工作,请使用以下命令:
In[]:df_fx_rates['Date'].max()

输出将如下所示,其中命令的结果将在 Notebook 中显示。在此数据集中,下载时的最大日期是 2020-05-07,日期格式为 YYYY-MM-DD

图片

  1. 在上一步中,我们确信我们的过滤器将使用正确的 Date 值,因此我们将创建一个新的 DataFrame,其中只包含一个特定的日期值,以便在后续步骤中合并结果。新的 DataFrame 命名为 df_fx_rates_max_date,它是通过 Date 字段过滤原始 DataFrame df_fx_rates 的结果,其中只返回计算出的最大 Date 值。我们将在 Notebook 中添加以下 head() 函数以验证结果:
In[]:df_fx_rates_max_date = df_fx_rates[df_fx_rates.Date==df_fx_rates['Date'].max()]
df_fx_rates_max_date.head()

输出将如下所示,其中命令的结果将在 Notebook 中显示。新的 DataFrame,命名为 df_fx_rates_max_date,将只有一个包含 34 列的记录。每一列将代表使用三位国家代码的最新可用货币值,例如 EUR

图片

  1. 我们仍然需要更多的工作来将此数据与我们的原始债券 DataFrame 合并。我们需要使用 transpose() 函数对其进行转换,这将所有列转换为行。在其他技术中,这个概念被称为 pivot、crosstab 或 crosstable;这在 第四章“创建您的第一个 pandas DataFrame”中已有更详细的介绍。结果存储在一个名为 df_rates_transposed 的新 DataFrame 中。我们重命名列以使其更容易处理。我们还需要运行以下 head() 命令来预览结果:
In[]:df_rates_transposed = df_fx_rates_max_date.transpose()
df_rates_transposed.columns.name = 'Currency'
df_rates_transposed.columns = ['Currency_Value']
df_rates_transposed.head(10)

输出将如下所示,其中新的 DataFrame,命名为 df_rates_transposed,被显示。在这里,所有列都已被转换为行:

图片

  1. 目标是使我们的参考表具有所有按货币值排列的外汇汇率,并且格式相同。然而,请注意,在以下图表的第一行中,Date 值与需要具有相同数据类型的 Currency_values 混合在一起。在整个本书中,需要将符合和一致的数据值表示为结构化数据的需求得到了加强,因此我们将通过删除 Date 记录来清理 DataFrame。我们还将使用 reindex() 函数使其在下一步中更容易连接,然后运行以下 head() 命令来验证结果:
In[]:df_rates_transposed = df_rates_transposed.drop('Date')
df_rates_transposed = df_rates_transposed.reindex()
df_rates_transposed.head()

输出将如下所示,其中新的 DataFrame,命名为 df_rates_transposed,显示方式与之前相同,但现在已删除 Date 记录。这意味着第一行将是 EUR,其 Currency_Value1.0783

图片

  1. 现在我们已经准备好使用共同的 Currency 连接键字段将转换和清洗后的外汇汇率与我们的原始债券源合并。因为我们希望从 df_greenbonds 源获取所有记录,并且只匹配 df_rates_transposed 中的值,所以我们将使用左连接。为了显示和验证结果,我们使用以下 head() 命令:
In[]:df_greenbonds_revised = df_greenbonds.merge(df_rates_transposed, how='left', left_on='Currency', right_index=True)
df_greenbonds_revised.head()

输出将如下所示,其中左连接的结果存储在 df_greenbonds_revised DataFrame 中。以下截图显示了一个包含 5 行和 11 列的表格。它包括一个标题行和未标记的索引值:

图片

如前图所示,请确保向右滚动以查看包括了一个名为 Currency_Value 的新列。

  1. 持续运行head()命令以验证每一步的结果的优势在于,你可以在准备和清理数据以进行进一步分析的同时对数据进行观察。在先前的屏幕截图中,我们可以看到Currency_Value中的null()值,显示为NaN。我们在第五章中介绍了如何处理 NaN 值,在 Python 中收集和加载数据。这是由于左连接的结果,这是预期的,因为 FX 汇率源数据中没有USD的值。这很有道理,因为你不需要转换美元的货币。然而,当我们尝试从这个列的值中创建计算时,这将会产生影响。在这种情况下,解决方案是将所有的NaN值转换为1,因为美元的 FX 汇率转换是 1。运行此命令后不会有输出:
In[]:df_greenbonds_revised["Currency_Value"].fillna(1, inplace=True)
  1. 由于我们的 CSV 文件源不包括数据字典,因此每个字段的定义数据类型是未知的。我们可以通过应用astype()函数来解决数据值中的任何不一致。我们将关注我们用于计算本地货币率的两个列,将它们转换为float类型的dtype。运行此命令后不会有输出:
In[]:df_greenbonds_revised["Currency_Value"] = df_greenbonds_revised.Currency_Value.astype(float)
df_greenbonds_revised["USD Equivalent"] = df_greenbonds_revised["USD Equivalent"].astype(float)
  1. 现在,我们已准备好在现有的 DataFrame 中创建一个新的计算列,该列将USD Equivalent列除以Currency_Value。结果将存储在同一 DataFrame 中的新列Local CCY中。运行此命令后不会有输出:
In[]:df_greenbonds_revised['Local CCY'] = df_greenbonds_revised['USD Equivalent']/df_greenbonds_revised['Currency_Value']
  1. 现在,我们可以将特定列的数据类型转换回整数,并通过明确标识它们来关注关键列。我们可以通过以下命令来完成:
In[]:df_greenbonds_revised['Local CCY'] = df_greenbonds_revised['Local CCY'].astype(int)
df_greenbonds_revised['USD Equivalent'] = df_greenbonds_revised['USD Equivalent'].astype(int)
df_greenbonds_revised[['ISIN', 'Currency', 'USD Equivalent', 'Currency_Value', 'Local CCY']]

输出将如下所示,其中前面命令的结果将在笔记本中显示。原始列“美元等值”以整数形式显示,现在我们可以在“货币值”右侧看到“本地货币”列:

图片

  1. 为了分析结果,让我们按货币对数据进行分组,并使用以下命令仅对USD EquivalentLocal CCY字段求和:
In[]:df_greenbonds_revised[['Currency', 'USD Equivalent', 'Local CCY']].groupby(['Currency']).sum().astype(int)

输出将如下所示,其中数据现在按货币聚合,同时还会显示USD EquivalentLocal CCY的总和:

图片

  1. 另一种分析类型是,通过使用matplotlib库的plot()函数创建水平条形图来直观地查看数据按货币的分布情况:
In[]:df_greenbonds_revised[['Currency', 'USD Equivalent']].groupby(['Currency']).size().plot(kind='barh');

输出将如下所示:

图片

如你所见,大量的绿色债券是以美元发行的,因为与其他货币相比,它拥有最大的条形图,并且差距很大。仅通过查看这些数据,无法明显看出这种情况的原因,因此需要进一步的分析。当你向组织内部的其他人展示你的发现时,这通常是现实情况,将数据混合在一起可以提供更多见解,但同时也导致了更多未解决的问题。这反过来又导致了需要将更多数据添加到现有来源中的需求。在何时停止混合更多数据之间找到平衡是具有挑战性的,因此鼓励添加增量里程碑来展示你的发现。

摘要

通过这样,我们已经在一项综合练习中走过了这本书中涵盖的许多不同概念。在本章中,我们更多地了解了可用于分析的现实世界数据源。我们还创建了一个可重复的工作流程,可以概括为:收集外部数据源,将它们合并在一起,然后分析结果。由于我们知道与数据打交道的现实情况永远不会那么简单直接,所以我们探讨了与之相关的固有挑战。我们将收集多个来源的步骤、转换它们、清洗、合并、分组和可视化结果的过程进行了分解。你越是在实际操作中与数据打交道,就越容易将这些概念应用到任何数据集上,同时保持基础不变。当你在处理数据时提高你的数据素养技能,你会注意到语法和工具会发生变化,但解决问题的挑战和机遇保持不变。我鼓励你通过持续学习更多关于数据的知识来继续投资自己。我希望你发现这是一段像我一样充实的人生旅程!

进一步阅读

第十七章

参考文献列表

书中引用的作品来源于以下来源:

  • Belle Selene Xia, 彭公(2015),《通过数据分析的商业智能》,Benchmarking,第 21 卷第 2 期,第 300-311 页。DOI:10.1108/BIJ-08-2012-0050

  • Edward Loper, Ewan Klein, 和 Steven Bird,《使用 Python 进行自然语言处理》,O'Reilly Media,978-0-596-51649-9

  • W. Francis 和 H. Kucera,《语言学系,布朗语料库手册》

    布朗大学,美国罗德岛州普罗维登斯,(1979)

  • Buneman Peter,《半结构化数据》,homepages.inf.ed.ac.uk/opb/papers/PODS1997a.pdf

  • Roy Thomas Fielding,《网络软件架构的设计:架构风格》,www.ics.uci.edu/~fielding/pubs/dissertation/top.htm,访问日期:2019 年 12 月 23 日。

  • C.J. Hutto & Eric Gilbert,《VADER:用于社交媒体文本情感分析的一种节省规则的模型》。第八届国际网络日志和社会媒体会议(ICWSM-14)。密歇根州安阿伯,2014 年 6 月。

  • Wes McKinney,《Python 中的数据结构用于统计计算》,第 9 届 Python 科学会议论文集。第 445 卷。2010 年。

  • Python | Pandas DataFrame,GeeksforGeeks:一个为极客提供的计算机科学门户网站:www.geeksforgeeks.org/python-pandas-dataframe/

  • Christopher C. Shilakes 和 Julie Tylman,《企业信息门户》(PDF):web.archive.org/web/20110724175845/http://ikt.hia.no/perep/eip_ind.pdf。1998 年 11 月 16 日。

posted @ 2025-10-27 09:00  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报