Python-和-Jupyter-数据科学入门-全-
Python 和 Jupyter 数据科学入门(全)
零、前言
随着行业继续重视其重要性,数据科学正变得越来越流行。 开源软件的最新进展已使这一学科为广泛的人们所接受。 在这本书中,我们展示了 Jupyter 笔记本如何与 Python 一起用于各种数据科学应用。 除了是进行数据探索的理想“虚拟游乐场”之外,Jupyter 笔记本同样适用于创建可重现的数据处理管道,可视化和预测模型。 通过将 Python 与 Jupyter 笔记本结合使用,数据科学提出的许多挑战变得易于概念化和实现。 这是通过利用 Python 库实现的,该库为更复杂的基础算法提供了抽象。 结果是数据科学对于初学者来说变得非常容易上手。 此外,Python 生态系统非常强大,并且每年都在增长。 因此,希望继续学习本书所涵盖主题的学生将找到极好的资源。
到本书结尾,您将具备使用 Python 分析数据和有效使用 Jupyter 笔记本的能力。
这本书涵盖的内容
第 1 课, “Jupyter 基础知识”,涵盖了 Jupyter 中的数据分析基础。 我们将从 Jupyter 的用法说明和功能入手,例如魔术功能和制表符补全。 然后,我们将过渡到特定于数据科学的材料。 我们将在实时 Jupyter 笔记本中进行探索性分析。 我们将使用视觉辅助,例如散点图,直方图和提琴图,以加深我们对数据的理解。 我们还将执行简单的预测建模。
第 2 课,“数据清理和高级机器学习”显示了如何在 Jupyter 笔记本中训练预测模型。 我们将讨论如何计划机器学习策略。 本课还说明了机器学习术语,例如监督学习,非监督学习,分类和回归。 我们将讨论使用 Scikit-learn 和 Pandas 预处理数据的方法。
第 3 课, “Web 抓取和交互式可视化”解释了如何剪贴网页表,然后使用交互式可视化来研究数据。 我们将首先研究 HTTP 请求的工作方式,重点是 GET 请求及其响应状态代码。 然后,我们将进入 Jupyter 笔记本,并使用 Requests 库通过 Python 发出 HTTP 请求。 我们将看到 Jupyter 如何用于在笔记本中呈现 HTML 以及可以与之交互的实际网页。 发出请求后,我们将看到如何使用 BeautifulSoup 来解析 HTML 中的文本,并使用该库来爬取表格数据。
这本书需要什么
本书将要求以下最低硬件要求:
- 处理器:Intel i5(或等效版本)
- 内存:8GB RAM
- 硬盘:10 GB
- 互联网连接
在本书中,我们将使用 Python 和 Jupyter 笔记本运行代码。 此外,还需要 Anaconda 环境才能运行 Python 和 Jupyter 笔记本。 请确保在计算机上安装了以下软件:
- Python 3.5+
- Anaconda 4.3+
Anaconda 安装随附的 Python 库:
- Matplotlib 2.1.0 以上
- IPython 6.1.0+
- Requests 2.18.4+
- BeautifulSoup4 4.6.0+
- NumPy 1.13.1+
- Pandas 0.20.3+
- Scikit-learn 0.19.0+
- Seaborn 0.8.0+
- Bokeh 0.12.10+
需要手动安装的 Python 库:
- mlxtend
- version_information
- ipython-sql
- pdir2
- GraphViz
安装和设置
在开始本书之前,我们将安装由 Python 和 Jupyter 笔记本组成的 Anaconda 环境。
安装 Anaconda
- 在浏览器中访问这个页面。
- 单击 Windows,Mac 或 Linux,具体取决于您使用的操作系统。
- 接下来,单击下载选项。 确保您下载了最新版本(3.6)。
- 下载后打开安装程序。
- 按照安装程序中的步骤就可以了! 您的 Anaconda 发行版已准备就绪。ix
更新 Jupyter 和安装依赖项
-
搜索 Anaconda 提示符并打开它。
-
键入以下命令来更新 Conda 和 Jupyter:
#Update conda conda update conda #Update Jupyter conda update jupyter #install packages conda install numpy conda install pandas conda install statsmodels conda install matplotlib conda install seaborn -
要从 Anaconda 提示符打开 Jupyter 笔记本,请使用以下命令:
jupyter notebook
这本书适合谁
这本书最适合对数据分析感兴趣的专业人士和学生。 涵盖的主题与各行各业的各种职位描述有关。 为了获得最佳体验,您应该具有编程基础知识和一些 Python 经验。 特别是,熟悉 Python 库(例如 Pandas,Matplotlib 和 Scikit-learn)将很有用。
约定
在本书中,您将找到许多可以区分不同类型信息的文本样式。 以下是这些样式的一些示例,并解释了其含义。
文本中的代码字和 Python 语言关键字如下所示:“使用新创建的空白笔记本,单击顶部的单元格并键入print('hello world')”
文件夹名称,文件名,文件扩展名,路径名,包括文本中的文件名,如下所示:“头文件boost/asio.hpp包含使用 Asio 库所需的大多数类型和功能”。
代码块设置如下:
y = df['MEDV'].copy()
del df['MEDV']
df = pd.concat((y, df), axis=1)
新术语和重要词以粗体显示。 您在屏幕上看到的字词,例如在菜单或对话框中的字样,如下所示:“单击右上角的新建,然后从下拉菜单中选择一个内核 。”
重要的新编程术语用粗体显示。 概念术语以斜体显示。
注意
有关主题的重要重要附加信息如下所示,如侧边栏中所示。
提示
重要说明,提示和技巧如下所示。
一、Jupyter 基础知识
Jupyter 笔记本是,是使用 Python 的数据科学家最重要的工具之一。 这是因为它们是开发可重现的数据分析管道的理想环境。 可以在单个笔记本内全部加载,转换和建模数据,在此方便快捷地测试代码并探索思路。 此外,所有这些都可以使用格式文本“内联”记录,因此您可以自己做笔记,甚至生成结构化的报告。
其他类似的平台(例如 RStudio 或 Spyder)为用户提供了多个窗口,这些窗口促进了繁重的任务,例如在周围复制和粘贴代码以及重新运行已经执行的代码。 这些工具还倾向于涉及读取求值提示循环(REPL),其中代码在已节省内存的终端会话中运行。 这种开发环境不利于可重复性,也不适合开发。 Jupyter 笔记本通过为用户提供一个在其中执行代码段并内联显示输出的窗口,解决了所有这些问题。 这使用户可以有效地开发代码,并允许他们回顾以前的工作以供参考,甚至进行更改。
我们将通过确切地解释什么是 Jupyter 笔记本开始本课,并继续讨论为什么它们在数据科学家中如此受欢迎。 然后,我们将一起打开一个笔记本,并进行一些练习以了解如何使用该平台。 最后,我们将深入研究第一个分析并对“基本功能和特性”进行探索性分析。
课程目标
在本课程中,您将:
- 了解什么是 Jupyter 笔记本及其为何对数据分析有用
- 使用 Jupyter 笔记本功能
- 学习 Python 数据科学库
- 执行简单的探索性数据分析
注意
本书中的所有代码都可以在代码包中作为特定于课程的 IPython 笔记本使用。 本书中的所有色图也可在代码包中找到。
基本功能和特性
在本节中,我们首先通过示例和讨论来证明 Jupyter 笔记本的有用性。 然后,为了涵盖面向初学者的 Jupyter 笔记本的基础知识,我们将在启动平台和与平台交互方面看到它们的基本用法。 对于以前使用 Jupyter 笔记本的用户,这主要是评论。 但是,您当然也会在该主题中看到新事物。
副题 A:什么是 Jupyter 笔记本,为什么有用?
Jupyter 笔记本是在本地运行的 Web 应用,其中包含实时代码,方程式,图形,交互式应用和 Markdown 文本。 标准语言是 Python,这就是我们将在本书中使用的。 但是,请注意,支持多种选择。 这包括其他主导数据科学语言 R:

那些熟悉 R 的人会知道 R Markdown。 Markdown 文档允许将 Markdown 格式的文本与可执行代码结合在一起。 Markdown 是一种用于在网络上设置样式的简单语言。 例如,大多数 GitHub 存储库都有一个README.md Markdown 文件。 此格式对于基本文本格式很有用。 它可以与 HTML 媲美,但允许更少的自定义。 Markdown 中常用的符号包括将文本变成标题的哈希(#),用于插入超链接的方括号和圆括号,以及用于创建斜体或粗体文本的星号:

了解了 Markdown 的基础知识之后,让我们回到 R Markdown,其中 Markdown 文本可以与可执行代码一起编写。 Jupyter 笔记本提供了与 Python 相同的功能,尽管我们将看到,它们的功能与 R Markdown 文档完全不同。 例如,除非另有说明,否则 R Markdown 假定您正在编写 Markdown,而 Jupyter 笔记本假定您正在输入代码。 这使使用 Jupyter 笔记本进行快速开发和测试更具吸引力。
从数据科学的角度来看,Jupyter 笔记本电脑有两种主要类型,具体取决于使用方式:实验室型和可交付使用。
实验室样式的笔记本旨在用作研究期刊的编程类似物。 这些应该包含您为加载,处理,分析和建模数据所做的所有工作。 这里的想法是记录您所做的一切,以备将来参考,因此通常不建议删除或更改以前的实验室样式的笔记本。 最好在分析过程中累积多个带日期戳的笔记本版本,以防您想回顾以前的状态。
可交付使用的笔记本旨在作为展示,并且应仅包含实验室样式笔记本的某些部分。 例如,这可能是一个有趣的发现,可以与您的同事共享,可以为管理者提供深入的分析报告,也可以为利益相关者总结重要发现。
在这两种情况下,重要的概念都是可重复性。 如果您一直在努力记录软件版本,则收到报告的任何人都可以重新运行笔记本并计算与您相同的结果。 在可重复性变得越来越困难的科学界,这是新鲜空气。
子主题 B:浏览平台
现在,我们将打开 Jupyter 笔记本并开始学习界面。 在这里,我们将假设您没有平台的先验知识,然后介绍基本用法。
介绍 Jupyter 笔记本
-
导航到终端中的随附材料目录。
注意
在 Mac 或 Linux 等 Unix 机器上,可以使用
ls显示目录内容,并使用cd更改目录来完成命令行导航。在 Windows 计算机上,使用
dir显示目录内容,而使用cd更改目录。 例如,如果要将驱动器从C:更改为D:,则应执行d:来更改驱动器。 -
通过在终端中键入以下内容,在此处启动新的本地笔记本服务器:
jupyter notebook默认浏览器的新窗口或新标签页将打开笔记本仪表盘至工作目录。 在这里,您将看到其中包含的文件夹和文件的列表。
-
单击文件夹以导航到该特定路径,然后通过单击打开文件。 尽管 Jupyter 的主要用途是编辑 IPYNB 笔记本文件,但 Jupyter 也可以用作标准的文本编辑器。
-
我们可以看到
NotebookApp正在本地服务器上运行。[I 20:03:01.045 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/ ?token=e915bb06866f19ce462d959a9193a94c7c088e81765f9d8a转到该 HTTP 地址将在浏览器窗口中加载该应用,就像启动该应用时自动完成的一样。 关闭窗口不会停止应用; 这应该从终端通过键入
Ctrl + C完成。 -
通过在终端中键入
Ctrl + C关闭应用。 您可能还需要输入y进行确认。 同时关闭 Web 浏览器窗口。 -
加载 NotebookApp 时,有多种可用选项。 在终端中,通过运行以下命令查看可用选项的列表:
jupyter notebook –-help -
一种这样的选择是指定一个特定的端口。 通过运行以下命令,在本地端口
9000上打开NotebookApp:jupyter notebook --port 9000 -
创建新的 Jupyter 笔记本的主要方法是从 Jupyter 仪表板。 单击右上角的“新建”,然后从下拉菜单中选择一个内核(即,在“笔记本”部分中选择某些内容):
![Introducing Jupyter Notebooks]()
内核为笔记本提供编程语言支持。 如果您在 Anaconda 中安装了 Python,则该版本应为默认内核。 Conda 虚拟环境也将在此处提供。
注意
虚拟环境是在同一台计算机上管理多个项目的绝佳工具。 每个虚拟环境可能包含不同版本的 Python 和外部库。 Python 具有内置的虚拟环境。 但是,Conda 虚拟环境与 Jupyter 笔记本集成得更好,并拥有其他出色的功能。 该文档位于。
-
使用新创建的空白笔记本,在顶部的单元格中单击,然后键入
print('hello world')或写入屏幕的任何其他代码段。 通过单击单元格并按Shift + Enter或在“单元格”菜单中选择“运行单元格”来执行它。单元运行时,代码的任何
stdout或stderr输出将显示在下方。 此外,写在最后一行的对象的字符串表示形式也会显示在中。 这非常方便,尤其是对于显示表格而言,但有时我们不希望显示最终对象。 在这种情况下,可以在行的末尾添加分号(;)以抑制显示。默认情况下,新单元格期望并运行代码输入; 但是,可以将它们更改为呈现 Markdown。
-
单击进入一个空单元格并将其更改为接受 Markdown 格式的文本。 这可以通过工具栏中的下拉菜单图标或通过从单元格菜单中选择 Markdown 来完成。 在此处写一些文本(任何文本都可以),确保使用 Markdown 格式符号,例如
#。 -
将重点放在笔记本顶部的工具栏上:

工具栏中有一个“播放”图标,可用于运行单元格。 但是,正如我们稍后将看到的那样,使用键盘快捷键`Shift + Enter`来运行单元格更加方便。 在其旁边是一个停止图标,可以使用来停止运行单元。 例如,这在单元运行时间过长时非常有用:

可以从**插入**菜单中手动添加新单元格:

可以使用图标或通过从**编辑**菜单中选择选项来复制,粘贴和删除单元格:


单元也可以通过以下方式上下移动:

**单元格**菜单下有一些有用的选项,可以运行一组单元格或整个笔记本:

- 尝试使用工具栏选项来上下移动单元格,插入新单元格以及删除单元格。
关于这些笔记本电脑要了解的重要一点是单元之间的共享内存。 这非常简单:工作表中存在的每个单元格都可以访问全局变量集。 因此,例如,在一个单元格中定义的函数可以从任何其他单元调用,并且同样适用于变量。 正如人们所期望的,函数范围内的任何内容都不是全局变量,只能从该特定函数中进行访问。
- 打开内核菜单以查看选择。 如果内核死了,内核菜单对于停止脚本执行和重新启动笔记本电脑很有用。 也可以随时在此处交换内核,但是由于对可重复性的考虑,建议不要在单个笔记本中使用多个内核。
- 打开“文件”菜单以查看选择。 “文件”菜单包含用于下载各种格式的笔记本的选项。 特别是,建议保存您的笔记本的 HTML 版本,其中的内容是静态呈现的,可以在 Web 浏览器中“按预期方式”打开和查看。
笔记本名称将显示在左上角。 新笔记本将自动命名为**无标题**。
- 通过单击左上角的当前名称并输入新名称来更改 IPYNB 笔记本文件的名称。 然后,保存文件。
- 关闭网络浏览器中的当前选项卡(退出笔记本电脑),然后转到“Jupyter 仪表板”选项卡,该选项卡仍应处于打开状态。 (如果未打开,请通过复制并从终端粘贴 HTTP 链接来重新加载它。)
由于我们没有关闭笔记本电脑,因此我们保存并退出了笔记本电脑,在 Jupyter 仪表板的**文件**部分中,它的名称旁边会有一个绿皮书符号,并将列为最后修改日期旁边的**运行**。 可以从此处关闭笔记本电脑。
- 选中您正在使用的笔记本(名称左侧的复选框),然后单击橙色的关闭按钮,退出该笔记本:

注意
如果您打算花费大量时间使用 Jupyter 笔记本,则值得学习键盘快捷键。 这将大大加快您的工作流程。 要学习的特别有用的命令是用于手动添加新单元格并将单元格从代码转换为 Markdown 格式的快捷方式。 单击帮助菜单中的键盘快捷方式,以了解操作方法。
子主题 C:Jupyter 功能
Jupyter 具有的许多吸引人的功能,这些特征使高效的 Python 编程成为可能。 这些包括各种各样的东西,从查看文档字符串的方法到执行 Bash 命令。 让我们在本节中一起探讨其中一些功能。
注意
可以在以下位置找到 IPython 的官方文档。 它包含有关我们将在此处和其他地方讨论的功能的详细信息。
探索 Jupyter 的一些最有用的功能
-
从 Jupyter 仪表板中,导航到
lesson-1目录,然后通过选择它来打开lesson-1-workbook.ipynb文件。 Jupyter 笔记本的标准文件扩展名是.ipynb,它在被称为 IPython 笔记本时就引入了。 -
向下滚动到 Jupyter 笔记本中的
Subtopic C: Jupyter Features。 我们首先回顾基本的键盘快捷键。 这些特别有助于避免不得不如此频繁地使用鼠标,这将大大加快工作流程。 这是最有用的键盘快捷键。 学习使用这些功能将极大地改善您使用 Jupyter 笔记本的体验以及您自己的效率:Shift + Enter用于运行单元格Esc键用于离开单元格M键用于将单元格更改为 Markdown(按Esc后)Y键用于更改要编码的单元格(按Esc后)- 箭头键移动单元格(按
Esc后) Enter键用于输入单元格
从快捷方式继续,帮助选项对初学者和经验丰富的编码人员都非常有用。 它可以为每个不确定的步骤提供指导。
通过在任何对象的末尾添加问号并运行单元格,用户可以获得帮助。 Jupyter 找到该对象的文档字符串,并在应用底部的弹出窗口中将其返回。
-
运行“获得帮助”部分的单元格,并查看 Jupyter 如何在笔记本底部显示文档字符串。 在此部分中添加一个单元格,并获得有关所选对象的帮助:
![Explore some of Jupyter's most useful features]()
制表符补全可用于执行以下操作:
- 导入外部库时列出可用的模块
- 列出导入的外部库的可用模块
- 功能和变量补全
当您需要了解模块的可用输入参数,探索新库,发现新模块或只是加快工作流程时,尤其有用。 它们将节省写出变量名或函数的时间,并减少了拼写错误。 制表符的完成效果非常好,以至于您可能在今天以后难以在其他编辑器中编写 Python 代码!
-
单击“制表符完成”部分中的空代码单元,然后尝试按上面建议的方式使用制表符完成。 例如,第一个建议可以通过输入
import(包括后面的空格),然后按Tab键来完成:![Explore some of Jupyter's most useful features]()
-
最后但并非最不重要的 Jupyter 笔记本基本功能是魔术命令。 这些命令由一个或两个百分号组成。 以
%%开头的魔术将应用于整个单元格,而以%开头的魔术仅适用于该行。 在示例中看到时,这将是有意义的。滚动到
Jupyter Magic Functions部分,然后运行包含%lsmagic和%matplotlib inline:的单元格![Explore some of Jupyter's most useful features]()
%lsmagic列出了可用的选项。 我们将讨论并显示一些最有用的示例。 您可能会看到的最常见的魔术命令是%matplotlib inline,它允许 Matplotlib 图形显示在笔记本中,而无需显式使用plt.show()。计时功能非常方便,分为两种:标准计时器(
%time或%%time)和用于测量多次迭代的平均运行时间的计时器(%timeit和%%timeit)。 -
运行“计时器”部分中的单元格。 请注意使用一个和两个百分号之间的区别。
即使使用 Python 内核(如您目前所做的那样),也可以使用魔术命令来调用其他语言。 内置选项包括 JavaScript,R,Pearl,Ruby 和 Bash。 Bash 特别有用,因为您可以使用 Unix 命令来查找当前位置(
pwd),目录中的内容(ls),创建新文件夹(mkdir)以及写入文件内容(cat/head/tail)。 -
运行“使用笔记本中的 bash”部分中的第一个单元格。 此单元格将一些文本写入工作目录中的文件,打印目录内容,打印空行,然后在删除之前回写新创建文件的内容:
![Explore some of Jupyter's most useful features]()
-
运行以下仅包含
ls和pwd的单元格。 请注意,我们不必显式使用 Bash 魔术命令来使它们起作用。有很多可以安装的外部魔术命令。 流行的是
ipython-sql,它允许在单元格中执行 SQL 代码。 -
如果尚未安装
ipython-sql,请立即安装。 打开一个新的终端窗口并执行以下代码:pip install ipython-sql![Explore some of Jupyter's most useful features]()
-
运行
&load_ext sql单元以将外部命令加载到笔记本中:

这样就可以连接到远程数据库,以便可以直接在笔记本内部执行查询(并记录在文档中)。
- 运行包含 SQL 示例查询的单元格:

在这里,我们首先连接到本地 sqlite 来源; 但是,该行可能指向本地或远程服务器上的特定数据库。 然后,我们执行一个简单的`SELECT`来显示如何将单元转换为运行 SQL 代码而不是 Python。
- 转到其他有用的魔术函数,我们将简要讨论一个有助于文档编制的函数。 该命令是
%version_information,但是 Jupyter 并没有标配它。 就像我们刚刚看到的 SQL 一样,可以使用pip从命令行安装它。
如果尚未完成,请立即使用`pip`从终端安装版本文档工具。 打开一个新窗口并运行以下代码:
```py
pip install version_information
```
安装后,可以使用`%load_ext version_information`将其导入任何笔记本电脑。 最后,一旦加载,它就可以用于显示笔记本电脑中每个软件的版本。
- 运行加载并调用
version_information命令的单元:

将 Jupyter 笔记本转换为 Python 脚本
您可以将 Jupyter 笔记本转换为 Python 脚本。 这等效于将每个代码单元的内容复制并粘贴到单个.py文件中。 Markdown 部分也包含在注释中。
可以从NotebookApp或在命令行中进行转换,如下所示:
jupyter nbconvert --to=python lesson-1-notebook.ipynb

例如,当您要使用pipreqs之类的工具确定笔记本的库要求时,此功能很有用。 该工具确定项目中使用的库,并将它们导出到requirements.txt文件中(可以通过运行pip install pipreqs进行安装)。
从包含.py文件的文件夹外部调用该命令。 例如,如果.py文件位于名为lesson-1的文件夹中,则可以执行以下操作:
pipreqs lesson-1/

lesson-1-workbook.ipynb的结果requirements.txt文件如下所示:
cat lesson-1/requirements.txt
matplotlib==2.0.2
numpy==1.13.1
pandas==0.20.3
requests==2.18.4
seaborn==0.8
beautifulsoup4==4.6.0
scikit_learn==0.19.0
子主题 D:Python 库
现在看到了 Jupyter 笔记本的所有基础知识,甚至还有一些更高级的功能,我们将注意力转移到本书中将要使用的 Python 库中。 通常,库会扩展默认的 Python 函数集。 常用标准库的示例是datetime,time和os。 这些之所以称为标准库,是因为它们是每个 Python 安装中的标准库。
对于使用 Python 的数据科学,最重要的库是外部的,这意味着它们不是 Python 的标准配置。
本书中将使用的外部数据科学库是 NumPy,Pandas,Seaborn,Matplotlib,Scikit-learn,Requests 和 Bokeh。 让我们简单介绍一下。
注意
使用行业标准导入库是个好主意,例如,通过import numpy as np;这样可以提高代码的可读性。 尽量避免执行from numpy import *之类的事情,因为您可能会无意间覆盖函数。 此外,将模块通过点(.)链接到库通常很不错,以提高代码的可读性。
- NumPy 提供了多维数据结构(数组),可以在其中执行的操作要比标准 Python 数据结构(例如,列表)快得多。 这部分是通过使用 C 在后台执行操作来完成的。NumPy 还提供了各种数学和数据操作功能。
- Pandas 是 Python 对 R
DataFrame的回答。 它以 2D 表格结构存储数据,其中列代表不同的变量,行代表样本。 Pandas 提供了许多方便的数据整理工具,例如填写 NaN 条目和计算数据的统计描述。 使用 PandasDataFrame将是本书的重点。 - Matplotlib 是一款受 MATLAB 平台启发的绘图工具。 熟悉 R 的可以将其视为 ggplot 的 Python 版本。 它是用于绘制图形的最受欢迎的 Python 库,并允许进行高级别的自定义。
- Seaborn 是作为 Matplotlib 的扩展,其中包括对数据科学有用的各种绘图工具。 一般来说,与使用诸如 Matplotlib 和 Scikit-learn 之类的库手动创建相同的东西相比,这样做可以更快地完成分析。
- Scikit-learn 是最常用的机器学习库。 它提供了一流的算法和非常优雅的 API,其中实例化了模型,然后使与数据匹配。 它还提供了对预测分析有用的数据处理模块和其他工具。
- Requests 是用于发出 HTTP 请求的转到库。 它使从网页获取 HTML 以及与 API 交互变得很简单。 为了解析 HTML,许多人选择 BeautifulSoup4,我们还将在本书中进行介绍。
- Bokeh 是一个交互式可视化库。 它的功能类似于 Matplotlib,但是允许我们将悬停,缩放,单击以及使用其他交互式工具添加到绘图中。 它还使我们能够在 Jupyter 笔记本内部渲染和玩游戏。
介绍了这些库之后,让我们回到笔记本并通过运行import语句加载它们。 这将使我们进入第一个分析,最后我们开始使用数据集。
导入外部库并设置绘图环境
-
打开
lesson 1Jupyter 笔记本,然后滚动到Subtopic D: Python Libraries部分。就像常规 Python 脚本一样,可以随时将库导入到笔记本中。 最佳做法是将您使用的大多数包放在文件顶部。 有时在笔记本中途加载东西是有意义的,这完全可以。
-
运行单元来导入外部库并设置绘图选项:
![Import Jupyter Notebooksplotting environment, setting upthe external libraries and set up the plotting environment]()
对于一个不错的笔记本设置,设置各种选项以及顶部的导入通常很有用。 例如,可以执行以下操作来将图形外观更改为比 Matplotlib 和 Seaborn 默认设置更美观的外观:
import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns # See here for more options: https://matplotlib.org/users/customizing.html %config InlineBackend.figure_format='retina' sns.set() # Revert to matplotlib defaults plt.rcParams['figure.figsize'] = (9, 6) plt.rcParams['axes.labelpad'] = 10 sns.set_style("darkgrid")
到目前为止,本书已经介绍了使用 Jupyter 笔记本进行数据科学的基础知识。 我们首先探索平台并找到围绕界面的方法。 然后,我们讨论了最有用的功能,其中包括制表符补全和魔术功能。 最后,我们介绍了将在本书中使用的 Python 库。
下一部分将非常互动,因为我们将使用 Jupyter 笔记本一起执行我们的第一个分析。
我们的第一个分析-波士顿住房数据集
到目前为止,本课程重点介绍 Jupyter 的功能和基本用法。 现在,我们将其付诸实践,并进行一些数据探索和分析。
我们将在本节中看到的数据集是所谓的波士顿住房数据集。 它包含有关波士顿市区各个地区房屋的美国人口普查数据。 每个样本对应一个唯一的区域,并具有大约十二个小节。 我们应该将样本视为行,将度量视为列。 该数据于 1978 年首次发布,非常小,仅包含约 500 个样本。
现在,我们对数据集的上下文有所了解,让我们为探索和分析制定一个粗略的计划。 如果适用,该计划将适应正在研究的相关问题。 在中,目标不是回答问题,而是演示 Jupyter 的实际操作并举例说明一些基本的数据分析方法。
我们进行此分析的一般方法是执行以下操作:
- 使用 Pandas
DataFrame将数据加载到 Jupyter 中 - 了解定量特征
- 寻找模式并提出问题
- 回答问题
子主题 A:使用 Pandas DataFrame将数据加载到 Jupyter 中
通常,数据存储在表中,这意味着可以将其保存为逗号分隔的变量(CSV)文件。 使用 Pandas 库,可以将这种格式以及许多其他格式作为DataFrame对象读入到 Python 中。 其他常见格式包括制表符分隔的变量(TSV),SQL 表和 JSON 数据结构。 实际上,Pandas 支持所有这些。 但是,在此示例中,我们不会以这种方式加载数据,因为可以直接通过 Scikit-learn 获得数据集。
注意
加载数据进行分析后,重要部分是确保数据干净。 例如,我们通常需要处理丢失的数据,并确保所有列都具有正确的数据类型。 我们在本节中使用的数据集已经清理完毕,因此我们无需担心。 但是,我们将在第二课中看到更混乱的数据,并探讨处理这些数据的技术。
加载波士顿住房数据集
-
在
lesson 1Jupyter 笔记本中,滚动到Our First Analysis: The Boston Housing Dataset的Subtopic A。可以使用
load_boston方法从sklearn.datasets模块访问波士顿住房数据集。 -
运行本节的前两个单元以加载波士顿数据集并查看
datastructures类型:![Load the Boston housing dataset]()
第二个单元格的输出告诉我们这是一个 Scikit-learn
Bunch对象。 让我们获得更多有关此的信息,以了解我们正在处理的内容。 -
运行下一个单元格,从 Scikit-learn
utils导入基础对象,并在我们的笔记本中打印文档字符串:![Load the Boston housing dataset]()
读取生成的文档字符串表明它基本上是一本字典,并且基本上可以这样对待。
-
通过运行下一个单元格来打印字段名称(即字典的键)。
我们发现这些字段是不言自明的:
['DESCR', 'target', 'data', 'feature_names']。 -
运行下一个单元以打印包含在
boston['DESCR']中的数据集描述。请注意,在此调用中,我们明确希望打印该字段值,以使笔记本以比字符串表示形式更可读的格式呈现内容(也就是说,如果我们只键入
boston['DESCR']而没有将其包装在print语句)。 然后,我们将数据集信息视为先前总结的:Boston House Prices dataset =========================== Notes ------ Data Set Characteristics: :Number of Instances: 506 :Number of Attributes: 13 numeric/categorical predictive :Median Value (attribute 14) is usually the target :Attribute Information (in order): - CRIM per capita crime rate by town … … - MEDV Median value of owner-occupied homes in $1000's :Missing Attribute Values: None这里特别重要的是特征说明(在
Attribute Information下)。 在分析过程中,我们将使用作为参考。注意
有关完整的代码,请参考
Lesson 1文件夹中的Lesson 1.txt文件。现在,我们将创建一个包含数据的 Pandas
DataFrame。 这出于以下几个原因是有好处的:我们的所有数据都将包含在一个对象中,可以使用有用且计算效率高的DataFrame方法,而其他诸如 Seaborn 之类的库都具有可以很好地与DataFrame集成的工具。在的情况下,我们将使用标准的构造器方法创建
DataFrame。 -
运行导入了 Pandas 的单元格,并为
pd.DataFrame检索文档字符串:![Load the Boston housing dataset]()
该文档字符串显示了
DataFrame输入参数。 我们要为数据输入boston['data'],并为标头使用boston['feature_names']。 -
运行接下来的几个单元格以打印数据,其形状和特征名称:
![Load the Boston housing dataset]()
查看输出,我们看到我们的数据在 2D NumPy 数组中。 运行命令
boston['data'].shape,分别返回长度(样本数)和特征数作为第一和第二输出。 -
通过运行以下命令将数据加载到 Pandas
DataFrame df中:df = pd.DataFrame(data=boston['data'], columns=boston['feature_names'])在机器学习中,正在建模的变量称为目标变量; 这就是您要预测的给定目标。 对于此数据集,建议的目标是
MEDV,即房屋价值中位数(以 1,000 美元计)。 -
运行下一个单元格以查看目标的形状:
![Load the Boston housing dataset]()
我们看到与特征具有相同的长度,这正是我们所期望的。 因此,可以将其作为新列添加到
DataFrame中。 -
通过使用以下命令运行单元格,将目标变量添加到
df:
```py
df['MEDV'] = boston['target']
```
- 为了将目标与我们的特征区分开来,将其存储在
DataFrame的前端可能会有所帮助。
通过使用以下命令运行单元格,将目标变量移至`df`的前面:
```py
y = df['MEDV'].copy()
del df['MEDV']
df = pd.concat((y, df), axis=1)
```
在这里,我们引入了一个虚拟变量`y`来保存目标列的副本,然后再将其从`DataFrame`中删除。 然后,我们使用 Pandas 连接函数将其与沿第 1 轴(而不是第 0 轴,后者组合行)的其余`DataFrame`组合在一起。
### 注意
您经常会看到点标记用于引用`DataFrame`列。 例如,以前我们可以完成`y = df.MEDV.copy()`。 但是,这不适用于删除列。 `del df.MEDV`会引发错误。
- 现在,数据已全部加载完毕,让我们看一下
DataFrame。 我们可以通过df.head()或df.tail()来查看数据,而len(df)可以确保样本数量符合我们的预期。
在接下来的几个单元格中运行,以查看`df`的头,尾和长度:


每行都标有一个索引值,如表左侧的粗体所示。 默认情况下,它们是一组整数,从 0 开始,每行递增一个。
- 打印
df.dtypes将显示每一列中包含的数据类型。
运行下一个单元格以查看每一列的数据类型。
对于此数据集,我们看到每个字段都是浮点数,因此很可能是连续变量,包括目标。 这意味着预测目标变量是一个回归问题。
- 我们要做的下一件事是通过处理所有丢失的数据来清理数据,Pandas 会自动将其设置为
NaN值。 可以通过运行df.isnull()来识别它们,该函数返回与df形状相同的布尔型DataFrame。 要获取每列的NaN数,我们可以执行df.isnull().sum()。
运行下一个单元格以计算每列中`NaN`值的数量:

对于此数据集,我们看到没有`NaN`,这意味着我们在清理数据方面没有立即要做的工作并且可以继续。
- 为了简化分析,在探索之前我们要做的最后一件事是删除一些列。 我们不会理会这些内容,而是会更详细地关注其余部分。
通过运行包含以下代码的单元格来删除一些列:
```py
for col in ['ZN', 'NOX', 'RAD', 'PTRATIO', 'B']:
del df[col]
```
子主题 B:数据探索
由于这是我们从未见过的全新数据集,因此这里的首要目标是了解数据。 我们已经看到了数据的文本描述,这对于定性理解很重要。 现在,我们将计算定量描述。
探索波士顿住房数据集
-
在 Jupyter 笔记本中导航到
Subtopic B: Data exploration,并运行包含df.describe()的单元格:![Explore Boston housing datasetexploringthe Boston housing dataset]()
这将计算各种属性,包括每列的平均值,标准差,最小值和最大值。 该表提供了有关所有内容分配方式的高级思路。 注意,我们通过在输出中添加
.T对结果进行了转换。 这将交换行和列。继续进行分析,我们将指定一组要关注的列。
-
运行定义了这些“焦点列”的单元格:
cols = ['RM', 'AGE', 'TAX', 'LSTAT', 'MEDV'] -
可以使用方括号从
df中选择此列的子集。 通过运行df[cols].head()显示DataFrame的此子集:![Explore Boston housing datasetexploringthe Boston housing dataset]()
提醒一下,让我们回顾一下这些列的含义。 从数据集文档中,我们有以下内容:
- RM average number of rooms per dwelling - AGE proportion of owner-occupied units built prior to 1940 - TAX full-value property-tax rate per $10,000 - LSTAT % lower status of the population - MEDV Median value of owner-occupied homes in $1000's为了在此数据中查找模式,我们可以开始使用
pd.DataFrame.corr计算成对相关性。 -
通过运行包含以下代码的单元格,为我们选择的列计算成对相关性:
df[cols].corr()![Explore Boston housing datasetexploringthe Boston housing dataset]()
此结果表显示了每组值之间的相关性得分。 较大的正面分数表示强烈的正面(即,朝着同一方向)相关。 如预期的那样,我们在对角线上看到最大值 1。
注意
皮尔逊系数定义为两个变量之间的协方差,除以它们的标准差的乘积:
![Explore Boston housing datasetexploringthe Boston housing dataset]()
协方差定义如下:
![Explore Boston housing datasetexploringthe Boston housing dataset]()
n是样本数,x[i]和y[i]是要累加的各个样本,X_bar和Y_bar是每组的平均值。
与其费力地看着前面的表格,不如用热图可视化它。 使用 Seaborn 可以轻松做到这一点。
-
如本课程前面所述,运行下一个单元格以初始化绘图环境。 然后,要创建热图,请运行包含以下代码的单元格:
import matplotlib.pyplot as plt import seaborn as sns %matplotlib inline ax = sns.heatmap(df[cols].corr(), cmap=sns.cubehelix_palette(20, light=0.95, dark=0.15)) ax.xaxis.tick_top() # move labels to the top plt.savefig('../figures/lesson-1-boston-housing-corr.png', bbox_inches='tight', dpi=300)![Explore Boston housing datasetexploringthe Boston housing dataset]()
我们称
sns.heatmap,并将成对相关矩阵作为输入。 我们在此处使用自定义调色板来覆盖 Seaborn 的默认设置。 该函数返回一个matplotlib.axes对象,该对象由变量ax引用。 然后将最终图形作为高分辨率 PNG 保存到figures文件夹。 -
对于数据集探索练习的最后一步,我们将使用 Seaborn
pairplot函数将您的数据可视化。使用 Seaborn 的
pairplot函数可视化DataFrame。 运行包含以下代码的单元格:sns.pairplot(df[cols], plot_kws={'alpha': 0.6}, diag_kws={'bins': 30})![Explore Boston housing datasetexploringthe Boston housing dataset]()
以前使用热图可视化相关性的简单概述,此图使我们可以更详细地查看这些关系。
查看对角线上的直方图,我们看到以下内容:
a:RM和MEDV具有最接近正态分布的形状。b:AGE偏向左侧,LSTAT偏向右侧(这似乎是违反直觉的,但偏斜是根据均值的位置,相对于最大值定义的)。c:对于TAX,我们发现大量分布在 700 左右。这从散点图中也很明显。
仔细观察右下角的MEDV直方图,我们实际上会看到类似于TAX的东西,其中有一个上限为 50,000 美元的大上限。 回想一下当我们执行df.describe()时,MDEV的最小值和最大值分别为 5k 和 50k。 这表明数据集中的房屋中位价上限为 50k。
子主题 C:使用 Jupyter 笔记本的预测分析的简介
继续我们对波士顿住房数据集的分析,我们可以看到它给我们带来了回归问题,在该问题中,我们根据给定的特征预测了一个连续的目标变量。 特别是,我们将预测中位数房屋价值(MEDV)。 我们将训练仅采用一项特征作为输入进行预测的模型。 这样,模型将在概念上易于理解,并且我们可以将更多精力放在 Scikit-learn API 的技术细节上。 然后,在下一课中,您将更轻松地处理相对复杂的模型。
使用 Seaborn 和 Scikit-learn 的线性模型
-
在 Jupyter 笔记本中滚动至
Subtopic C: Introduction to predictive analytics,然后在上方查看我们在上一节中创建的对图。 特别是,请查看左下角的散点图:![Linear models with Seaborn and scikit-learn]()
请注意,每间房屋的房间数(
RM)和低阶层人口的百分比(LSTAT)与房屋中位数(MDEV)。 让我们提出以下问题:给定这些变量,我们如何预测MDEV?为了帮助回答这个问题,让我们首先使用 Seaborn 可视化关系。 我们将与最佳拟合线性模型一起绘制散点图。
-
通过运行包含以下内容的单元,绘制散点图以及线性模型:
fig, ax = plt.subplots(1, 2) sns.regplot('RM', 'MEDV', df, ax=ax[0], scatter_kws={'alpha': 0.4})) sns.regplot('LSTAT', 'MEDV', df, ax=ax[1], scatter_kws={'alpha': 0.4}))![Linear models with Seaborn and scikit-learn]()
最佳拟合线是通过最小化普通最小二乘误差函数来计算的,当我们调用
regplot函数时,Seaborn 会自动执行。 还要注意线条周围的阴影区域,代表 95% 的置信区间。注意
通过获取垂直于最佳拟合线的仓中数据的标准差,有效地确定沿最佳拟合线的每个点的置信区间,可以计算出这 95% 的置信区间。 在实践中,这涉及 Seaborn 引导数据,该过程是通过随机采样替换产生新数据的过程。 自举的样本数是根据数据集的大小自动确定的,但也可以通过传递
n_boot参数来手动设置。 -
Seaborn 也可以用于绘制这些关系的残差。 通过运行包含以下内容的单元来绘制残差:
fig, ax = plt.subplots(1, 2) ax[0] = sns.residplot('RM', 'MEDV', df, ax=ax[0], scatter_kws={'alpha': 0.4}) ax[0].set_ylabel('MDEV residuals $(y-\hat{y})$') ax[1] = sns.residplot('LSTAT', 'MEDV', df, ax=ax[1], scatter_kws={'alpha': 0.4}) ax[1].set_ylabel('')![Linear models with Seaborn and scikit-learn]()
这些残差图上的每个点都是该样本(
y)与线性模型预测(ŷ)之间的差。 大于零的残差是模型会低估的数据点。 同样,小于零的残差是数据点,可能会被模型高估。这些图中的模式可以指示次优建模。 在前面的每种情况下,我们都在正区域看到对角线排列的散射点。 这些是由
MEDV的 50,000 美元上限引起的。RM数据很好地聚集在 0 周围,表明拟合良好。 另一方面,LSTAT似乎低于 0。 -
从可视化继续,可以通过计算均方误差来量化拟合。 现在,我们将使用 Scikit-learn 进行此操作。 通过运行包含以下内容的单元格,定义一个计算最佳拟合线和均方误差的函数:
def get_mse(df, feature, target='MEDV'): # Get x, y to model y = df[target].values x = df[feature].values.reshape(-1,1) ... ... error = mean_squared_error(y, y_pred) print('mse = {:.2f}'.format(error)) print()注意
有关完整的代码,请参考
Lesson 1文件夹中的Lesson 1.txt文件。在
get_mse函数中,我们首先分别将变量y和x分配给目标MDEV和从属特征。 通过调用values属性将它们强制转换为 NumPy 数组。 从属特征数组将重塑为 Scikit-learn 期望的格式; 仅在对一维特征空间建模时才需要这样做。 然后实例化模型并将其拟合到数据上。 对于线性回归,拟合包括使用普通最小二乘法(最小化每个样本的平方误差之和)来计算模型参数。 最后,在确定参数之后,我们预测目标变量并使用结果来计算 MSE。 -
通过运行包含以下内容的单元格,为
RM和LSTAT调用get_mse函数:get_mse(df, 'RM') get_mse(df, 'LSTAT')![Linear models with Seaborn and scikit-learn]()
比较 MSE,结果发现LSTAT的误差略低。 但是,回头看散点图,似乎对于LSTAT使用多项式模型,我们可能会取得更大的成功。 在下一个活动中,我们将通过使用 Scikit-learn 计算一个三阶多项式模型来对此进行测试。
暂时忘掉我们的波士顿住房数据集,请考虑另一种可能使用多项式回归的现实情况。 以下示例是对天气数据进行建模。 在以下图表中,我们看到加拿大不列颠哥伦比亚省温哥华的温度(线)和降水量(条形图):

这些字段中的任何一个都可能很适合四阶多项式。 例如,如果您有兴趣预测连续日期范围内的温度或降水,这将是一个非常有价值的模型。
活动 B:建立三阶多项式模型
将我们的注意力转移回波士顿住房数据集,我们想建立一个三阶多项式模型来与线性模型进行比较。 回想一下我们要解决的实际问题:在低阶层人口百分比的情况下,预测房屋中位数。 这种模式可以使潜在的波士顿购房者受益,他们关心他们的社区中有多少是低阶层。
给定LSTAT值,使用 Scikit-learn 拟合多项式回归模型来预测房屋中位数(MEDV)。 我们希望建立一个模型,该模型具有较低的均方误差(MSE)。
-
滚动到 Jupyter 笔记本中
Subtopic C底部的空白单元格。 这些可以在Activity标题下的线性模型 MSE 计算单元下找到。注意
在完成活动时,应使用代码填充这些空白单元格。 您可能需要在新单元格已填满时插入它们。 请根据需要这样做!
-
假定我们的数据包含在
DataFrame``df中,我们将首先使用以下内容提取依赖特征和目标变量:y = df['MEDV'].values x = df['LSTAT'].values.reshape(-1,1)这与我们之前对线性模型所做的相同。
-
通过使用
print(x[:3])打印前几个样本来检查x的外观:![Activity B: Building a Third-Order Polynomial Model]()
请注意,数组中的每个元素本身就是一个长度为 1 的数组。这是
reshape(-1,1)的功能,它是 Scikit-learn 期望的形式。 -
接下来,我们将把
x转换为“多项式特征”。 这样做的原理可能不会立即显而易见,但将在稍后进行解释。从 Scikit-learn 导入适当的转换工具并实例化三次多项式特征转换器:
from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=3) -
在这一点上,我们仅具有特征转换器的一个实例。 现在,让我们通过运行
fit_transform方法来使用它来转换LSTAT特征(存储在变量x中)。通过运行以下代码来构建多项式特征集:
x_poly = poly.fit_transform(x) -
通过使用
print(x_poly[:3])打印前几个样本来检查x_poly是什么样的。.![Activity B: Building a Third-Order Polynomial Model]()
与
x不同,现在每行中的数组的长度为 4,其中的值已计算为x[0],x[1],x[2]和x[3]。我们现在将使用此数据来拟合线性模型。 将特征标记为
a,b,c和d,我们将计算系数α[0],α[1],α[2]和α[3]的线性模型:![Activity B: Building a Third-Order Polynomial Model]()
我们可以插入
a,b,c和d的定义,以获得以下多项式模型,其中系数与以前相同:![Activity B: Building a Third-Order Polynomial Model]()
-
在计算 MSE 时,我们将以与以前相同的方式导入
LinearRegression类并构建线性分类模型。 运行以下命令:from sklearn.linear_model import LinearRegression clf = LinearRegression() clf.fit(x_poly, y) -
使用以下代码提取系数并打印多项式模型:
a_0 = clf.intercept_ + clf.coef_[0] # intercept a_1, a_2, a_3 = clf.coef_[1:] # other coefficients msg = 'model: y = {:.3f} + {:.3f}x + {:.3f}x^2 + {:.3f}x^3'\ .format(a_0, a_1, a_2, a_3) print(msg)![Activity B: Building a Third-Order Polynomial Model]()
为了获得实际的模型截距,我们必须添加
intercept_和coef_[0]属性。 然后,高阶系数由coef_的剩余值给出。 -
通过运行以下代码,确定每个样本的预测值并计算残差:
y_pred = clf.predict(x_poly) resid_MEDV = y - y_pred -
通过运行
print(resid_MEDV[:10])打印一些残值:

我们将尽快绘制这些图以与线性模型残差进行比较,但首先我们将计算 MSE。
- 运行以下代码以打印三阶多项式模型的 MSE:
```py
from sklearn.metrics import mean_squared_error
error = mean_squared_error(y, y_pred)
print('mse = {:.2f}'.format(error))
```

可以看出,与线性模型(38.5)相比,多项式模型的 **MSE** 明显更少。 通过取平方根,可以将该误差度量转换为以美元为单位的平均误差。 对多项式模型执行此操作,我们发现的平均误差中位数房屋价值仅为 5,300 美元。
现在,我们将通过绘制最佳拟合的多项式线和数据来可视化模型。
- 通过运行以下命令,将多项式模型与样本一起绘制:
```py
fig, ax = plt.subplots()
# Plot the samples
ax.scatter(x.flatten(), y, alpha=0.6)
# Plot the polynomial model
x_ = np.linspace(2, 38, 50).reshape(-1, 1)
x_poly = poly.fit_transform(x_)
y_ = clf.predict(x_poly)
ax.plot(x_, y_, color='red', alpha=0.8)
ax.set_xlabel('LSTAT'); ax.set_ylabel('MEDV');
```

在这里,我们通过在`x`值数组上计算多项式模型预测来绘制红色曲线。 `x`值数组是使用`np.linspace`创建的,导致 50 个值均匀排列在 2 和 38 之间。
现在,我们将绘制对应的残差。 尽管我们之前曾使用 Seaborn 进行过开发,但我们必须手动进行操作才能显示 Scikit-learn 模型的结果。 由于我们已经较早地计算了残差(作为`resid_MEDV`变量的参考),因此我们只需要在散点图上绘制此值列表即可。
- 通过运行以下命令绘制残差:
```py
fig, ax = plt.subplots(figsize=(5, 7))
ax.scatter(x, resid_MEDV, alpha=0.6)
ax.set_xlabel('LSTAT')
ax.set_ylabel('MEDV Residual $(y-\hat{y})$')
plt.axhline(0, color='black', ls='dotted');
```

与线性模型`LSTAT`残差图相比,多项式模型残差似乎更紧密地聚集在`y = 0`周围。 注意,`y`是样本`MEDV`,`ŷ`是预测值。 仍然存在清晰的模式,例如`x = 7`和`y = -7`附近的群集,这表明模型不是最佳的。
使用多项式模型成功建模数据后,让我们通过查看分类特征来结束本节。 特别是,我们将构建一组分类特征,并使用它们来更详细地探索数据集。
子主题 D:使用分类特征的细分分析
通常,我们发现数据集包含连续字段和分类字段。 在这种情况下,我们可以通过使用分类字段对连续变量进行分段来了解我们的数据并找到模式。
举一个具体的例子,假设您正在评估广告序列的投资回报率。 您可以访问的数据包含一些计算出的投资回报率(ROI)度量的度量。 每天计算这些值并记录,您正在分析上一年的数据。 您的任务是寻找以数据为依据的见解,以改善广告序列。 查看 ROI 的每日时间序列,您会看到每周的数据波动。 按星期几细分,您会发现以下 ROI 分布(其中 0 代表一周的第一天,而 6 代表最后一天)。

此清楚地表明,该广告序列在一周开始时获得了最大的投资回报率,随后逐渐减少。 因此,建议可能是减少下周的广告支出。 为了继续寻找见解,您还可以想象对按月分组的 ROI 重复相同的过程。
由于在我们正在使用的波士顿房屋数据集中没有任何分类字段,因此我们将通过有效离散连续字段来创建一个分类字段。 在我们的案例中,这将涉及将数据分为“低”,“中”和“高”类别。 重要的是要注意,我们并不是简单地创建分类数据字段来说明本节中的数据分析概念。 可以看到,这样做可以从数据中揭示洞察力,否则这些洞察力将很难被发现或完全不可用。
从连续变量创建分类字段,并进行分段可视化
-
向上滚动到 Jupyter 笔记本中的对图,我们在其中比较了
MEDV,LSTAT,TAX,AGE和RM:![Create categorical fieldscreatingcategorical fields from continuous variables and make segmented visualizations]()
查看包含
AGE的面板。 提醒一下,此特征定义为在 1940 年之前建造的自住单位比例。 我们将将此特征转换为分类变量。 转换后,我们将能够按照年龄类别对每个面板按颜色进行细分,以重新绘制该图形。 -
向下滚动至
Subtopic D: Building and exploring categorical features,然后单击进入第一个单元格。 输入并执行以下命令以绘制AGE累积分布:sns.distplot(df.AGE.values, bins=100, hist_kws={'cumulative': True}, kde_kws={'lw': 0}) plt.xlabel('AGE') plt.ylabel('CDF') plt.axhline(0.33, color='red') plt.axhline(0.66, color='red') plt.xlim(0, df.AGE.max());![Create categorical fieldscreatingcategorical fields from continuous variables and make segmented visualizations]()
注意,我们设置
kde_kws={'lw': 0}是为了绕过上图中绘制核密度估计值的过程。从图中的看,只有很少的样本具有较低的
AGE,而有更多的样本具有较高的AGE。 这通过最右边的分布的陡度来表示。红线表示分布中的 1/3 和 2/3 点。 观察分布分布与这些水平线相交的地方,我们可以看到,只有约 33% 的样本的
AGE小于 55,而 33% 的样本的AGE大于 90! 换句话说,三分之一的住房社区在 1940 年之前建造房屋的比例不到 55%。这些社区被认为是相对较新的社区。 另一方面,另外三分之一的住房社区在 1940 年之前建造了超过 90% 的房屋。这些房屋被认为是非常古老的。我们将使用红色水平线截取分布的位置作为将特征划分为类别的指导:相对较新,相对较旧和较旧。
-
将分段点设置为 50 和 85,通过运行以下代码来创建新的分类特征:
def get_age_category(x): if x < 50: return 'Relatively New' elif 50 <= x < 85: return 'Relatively Old' else: return 'Very Old' df['AGE_category'] = df.AGE.apply(get_age_category)在这里,我们使用非常方便的 Pandas 方法
apply,该方法将函数应用于给定的列或一组列。 在本例中为get_age_category时,所应用的函数应采用一个表示一行数据的参数,并为新列返回一个值。 在这种情况下,要传递的数据行只是一个值,即样本的AGE。注意
apply方法之所以出色,是因为它可以解决各种问题并允许易于阅读的代码。 但是,向量化方法(例如pd.Series.str)通常可以更快地完成同一件事。 因此,建议尽可能避免使用它,尤其是在处理大型数据集时。 在接下来的课程中,我们将看到一些向量化方法的示例。 -
通过在新单元格中键入
df.groupby('AGE_category').size()并运行它,检查我们将每个年龄组归类了多少个样本:![Create categorical fieldscreatingcategorical fields from continuous variables and make segmented visualizations]()
查看结果,可以看出两个类别的人数相当相等,而“非常老”的一类人大约大 40%。 我们有兴趣保持类的大小可比,以使每个类都有很好的表示,并且可以直接从分析中进行推断。
注意
并非总是可以将样本均匀地分配给类,在现实世界中,找到高度不平衡的类是很常见的。 在这种情况下,请务必牢记,对于人数不足的类别很难做出具有统计意义的声明。 类不平衡的预测分析可能特别困难。 以下博客文章提供了有关在进行机器学习时处理不平衡类的方法的出色总结。
让我们看看通过新函数
AGE_category进行细分时目标变量的分布情况。 -
通过运行以下代码制作提琴图:
sns.violinplot(x='MEDV', y='AGE_category', data=df, order=['Relatively New', 'Relatively Old', 'Very Old']);![Create categorical fieldscreatingcategorical fields from continuous variables and make segmented visualizations]()
提琴图显示了每个年龄类别的房屋中值分布的核密度估计。 我们看到它们都类似于正态分布。 “非常老”组的房价中位数样本最低,并且宽度相对较大,而其他组则更紧密地围绕其平均值。 青年组偏向高端,这可以从右半部分的放大和白点在分布主体内的粗黑线中的位置明显看出。
这个白点代表平均值,粗黑线跨越了大约 50% 的总体(填充到白点两侧的第一个分位数)。 黑色的细线代表箱线图的胡须,覆盖 95% 的人口。 通过将
inner='point'传递给sns.violinplot(),可以修改此内部可视化以显示各个数据点。 现在开始吧。 -
重做提琴图,将
inner='point'参数添加到sns.violinplot调用中:![Create categorical fieldscreatingcategorical fields from continuous variables and make segmented visualizations]()
为了进行测试,最好制作这样的图,以查看基础数据如何连接到视觉。 例如,我们可以看到相对新细分市场的房价中位数如何不低于约$ 16,000,因此因此分配尾部实际上不包含任何数据。 由于我们的数据集较小(仅约 500 行),我们可以看到每个细分市场都是这种情况。
-
重做先前的对图,但现在为每个
AGE类别包括颜色标签。 这可以通过简单地传递hue参数来完成,如下所示:cols = ['RM', 'AGE', 'TAX', 'LSTAT', 'MEDV', 'AGE_category'] sns.pairplot(df[cols], hue='AGE_category', hue_order=['Relatively New', 'Relatively Old', 'Very Old'], plot_kws={'alpha': 0.5}, diag_kws={'bins': 30});![Create categorical fieldscreatingcategorical fields from continuous variables and make segmented visualizations]()
查看直方图上的,对于
RM和TAX,每个段的基础分布显得相似。 另一方面,LSTAT分布看起来更加不同。 我们可以再次使用提琴图来更详细地关注它们。 -
制作一个提琴图,比较每个
AGE_category段的LSTAT分布:![Create categorical fieldscreatingcategorical fields from continuous variables and make segmented visualizations]()
与MEDV提琴图不同,在每个提琴图中,分布图的宽度大致相同,而这里的宽度随AGE的增加而增加。 以旧房屋为主的社区(“非常旧”的部分)的居民数量很少,甚至很多,而相对较新的社区更可能是较高阶级的人群,超过 95% 的样本的低阶级百分比低于“非常旧”的社区。 这是有道理的,因为相对而言,新街区会更昂贵。
总结
在本课程中,您已经了解了 Jupyter 中数据分析的基础。
我们从 Jupyter 的用法说明和功能开始,例如魔术功能和制表符补全。 然后,过渡到特定于数据科学的材料,我们介绍了使用 Python 进行数据科学最重要的库。
在课程的后半部分,我们在现场 Jupyter 笔记本中进行了探索性分析。 在这里,我们使用了视觉辅助工具,例如散点图,直方图和提琴图,以加深我们对数据的理解。 我们还执行了简单的预测建模,这是本书下一课的重点。
在下一课中,我们将讨论如何进行预测分析,在准备用于建模的数据时应考虑的事项以及如何使用 Jupyter 笔记本实现和比较各种模型。
二、数据清理和高级机器学习
数据分析的总体目标是发掘可操作的见解,从而带来积极的业务成果。 就预测分析而言,其目的是通过根据先前的趋势和模式确定目标的最有可能的未来结果来做到这一点。
预测分析的好处不仅限于大型技术公司。 只要有正确的数据,任何企业都可以找到从机器学习中受益的方法。
世界各地的公司都在收集大量数据,并使用预测分析来降低成本和增加利润。 一些最流行的例子来自科技巨头谷歌,Facebook 和亚马逊,它们大规模利用了大数据。 例如,Google 和 Facebook 根据预测性算法向您提供个性化广告,这些算法会猜测您最有可能点击的内容。 同样,鉴于先前的购买,Amazon 推荐您最有可能购买的个性化产品。
现代的预测分析是通过机器学习来完成的,其中训练计算机模型以从数据中学习模式。 正如我们在上一课中简要看到的那样,诸如 Scikit-learn 之类的软件可以与 Jupyter 笔记本一起使用,以有效地构建和测试机器学习模型。 我们将继续看到,Jupyter 笔记本是执行此类工作的理想环境,因为我们可以执行临时测试和分析,并轻松保存结果以供以后参考。
在本课程中,我们将通过在 Jupyter 笔记本中运行各种示例和活动来再次采用动手方法。 在上一课中我们看到了一些机器学习的示例,在这里,我们将采用一种慢得多,更周到的方法。 使用员工保留问题作为本课程的总体示例,我们将讨论如何进行预测分析,在准备用于建模的数据时应考虑哪些事项以及如何使用 Jupyter 笔记本实现和比较各种模型。
在本课程中,您将:
- 规划机器学习分类策略
- 预处理数据以准备进行机器学习
- 训练分类模型
- 使用验证曲线调整模型参数
- 使用降维来增强模型表现
准备训练预测模型
在这里,我们将介绍训练预测模型所需的准备工作。 尽管在技术上不如训练模型本身那么迷人,但不应轻易采取这一步骤。 在继续构建和训练可靠模型的细节之前,确保您有一个良好的计划非常重要。 此外,一旦您确定了正确的计划,就需要准备一些技术步骤来准备用于建模的数据。
注意
我们必须小心,不要深入研究技术任务的杂草,以至于看不到目标。
技术任务包括需要编程技能的事物,例如,构建可视化效果,查询数据库以及验证预测模型。 花费数小时来尝试实现特定功能或使图看起来正确很容易。 这样做肯定对我们的编程技能有所帮助,但是对于当前项目,我们不要忘记问自己是否真的值得我们花时间。
另外,请记住,Jupyter 笔记本非常适合此步骤,因为我们可以使用它们来记录我们的计划,例如,通过编写有关数据的粗略注释或我们感兴趣的模型列表。 在开始训练模型之前,最好的做法是更进一步,并制定出结构合理的计划。 这不仅可以帮助您在构建和测试模型时保持步入正轨,而且还可以让其他人在看到您的工作时了解您在做什么。
在讨论了准备工作之后,我们还将介绍准备训练预测模型的另一步骤,即清理数据集。 这是 Jupyter 笔记本电脑非常适合的另一项,因为它们为执行数据集转换和跟踪确切更改提供了理想的测试平台。 清理原始数据所需的数据转换会很快变得复杂且令人费解。 因此,跟踪您的工作非常重要。 如第一课所述,Jupyter 笔记本以外的工具只是不能提供有效执行此操作的很好的选择。
子主题 A:确定预测性分析计划
当制定用于进行预测建模的计划时,应首先考虑利益相关者的需求。 如果不能解决相关问题,那么完美的模型将毫无用处。 围绕业务需求规划策略可确保成功的模型将带来可操作的见解。
尽管原则上可以解决许多业务问题,但交付解决方案的能力将始终取决于必要数据的可用性。 因此,在可用数据源的上下文中考虑业务需求非常重要。 如果数据足够多,则影响不大,但是随着可用数据量变小,可以解决的问题范围也将减少。
这些想法可以形成用于确定预测分析计划的标准流程,如下所示:
- 查看可用数据,以了解可实际解决的业务问题的范围。 在此阶段,考虑可以解决的确切问题可能为时过早。 确保您了解可用的数据字段以及它们适用的时间范围。
- 通过与主要利益相关者交谈确定业务需求。 寻找一个问题,该解决方案将导致可行的业务决策。
- 通过考虑足够多样化和较大的特征空间的可用性来评估数据是否适合。 另外,还要考虑数据的条件:对于某些变量或时间范围,是否存在大量缺少值的块?
应该重复步骤 2 和 3,直到制定出切实可行的计划。 至此,您已经对模型输入将是什么以及输出期望值有了一个很好的了解。
一旦我们确定了可以通过机器学习解决的问题以及和适当的数据源,我们应该回答以下问题,为该项目奠定框架。 这样做将帮助我们确定可以用来解决问题的机器学习模型的类型:
-
训练数据是否标有我们要预测的目标变量?
如果答案是肯定的,那么我们将进行有监督的机器学习。 监督式学习具有许多现实世界的用例,而查找业务案例以对未标记的数据进行预测分析的情况要少得多。
如果答案是否定的,则您正在使用未标记的数据,因此在进行无监督的机器学习。 无监督学习方法的一个示例是聚类分析,其中将标签分配给每个样本的最近聚类。
-
如果数据被标记,那么我们是否正在解决回归或分类问题?
在回归问题中,目标变量是连续的,例如,以厘米为单位预测明天的降雨量。 在分类问题中,目标变量是离散的,我们正在预测类别标签。 分类问题最简单的类型是二进制,其中每个样本都分为两个类别之一。 例如,明天会下雨吗?
-
数据是什么样的? 有多少不同的来源?
考虑宽度和高度方面的数据大小,其中宽度表示列(特征)的数量,高度表示行的数量。 某些算法在处理大量特征方面比其他算法更有效。 通常,数据集越大,准确率越好。 但是,对于大型数据集,训练可能非常缓慢且占用大量内存。 始终可以通过对数据执行聚合或使用降维技术来减少这种情况。
如果有不同的数据源,可以将它们合并为到单个表中吗? 如果不是,那么我们可能要为每个模型训练模型,并为最终预测模型取整体平均值。 我们可能要执行此操作的示例是使用不同比例的各种时间序列数据集。 考虑一下我们有以下数据源:一张表,其中 AAPL 股票收盘价为每日时间范围,iPhone 销售数据为每月时间范围。
我们可以通过将每月销售数据添加到每日时间比例表中的每个样本,或按月对每日数据进行分组来合并数据,但是最好建立两个模型,一个用于每个数据集,然后结合使用最终预测模型中每个模型的结果。
子主题 B:为机器学习预处理数据
数据预处理对机器学习有巨大影响。 就像“吃什么就吃”的说法一样,模型的表现直接反映了所训练的数据。 许多模型取决于要转换的数据,因此连续特征值具有可比较的限制。 同样,分类特征应编码为数值。 尽管很重要,但是这些步骤相对简单,并且不需要很长时间。
注意
通常需要花费最长时间的预处理方面是清理凌乱的数据。 只需看一下这张饼图,它显示了来自特定调查的数据科学家大部分时间都在做什么:

要考虑的另一项是许多数据科学家正在使用的数据集的大小。 随着数据集大小的增加,混乱数据的普及率也随之增加,同时清理难度也越来越大。
通常,简单地删除丢失的数据不是最佳选择,因为很难证明丢弃大多数字段具有值的样本。 这样做可能会丢失有价值的信息,从而可能损害最终模型的表现。
数据预处理中涉及的步骤可以分为以下几类:
- 在公共字段上合并数据集,以将所有数据合并到一个表中
- 用于改善数据质量的特征工程,例如,使用降维技术来构建新特征
- 通过处理重复的行,不正确或缺失的值以及其他出现的问题来清理数据
- 通过标准化或标准化所需数据并将其分为训练和测试集来构建训练数据集
让我们探索进行预处理的一些工具和方法。
探索数据预处理工具和方法
-
通过执行
jupyter notebook从项目目录启动NotebookApp。 导航到Lesson-2目录并打开lesson-2-workbook.ipynb文件。 找到顶部附近的包加载单元,然后运行它。我们将从展示 Pandas 和 Scikit-learn 的一些基本工具开始。 然后,我们将更深入地研究重建缺失数据的方法。
-
向下滚动至
Subtopic B: Preparing data for machine learning,然后运行包含pd.merge?的单元格,以在笔记本中显示merge函数的文档字符串:![Explore data preprocessing tools and methods]()
如我们所见,该函数接受左右
DataFrame进行合并。 您可以指定一个或多个列进行分组以及如何对其进行分组,即使用左,右,外部或内部值集。 让我们来看一个使用中的例子。 -
退出帮助弹出窗口并运行包含以下示例
DataFrame的单元格:df_1 = pd.DataFrame({'product': ['red shirt', 'red shirt', 'red shirt', 'white dress'],\n", 'price': [49.33, 49.33, 32.49, 199.99]})\n", df_2 = pd.DataFrame({'product': ['red shirt', 'blue pants', 'white tuxedo', 'white dress'],\n", 'in_stock': [True, True, False, False]})在这里,我们将从头开始构建两个简单的
DataFrame。 可以看出,它们包含带有某些共享条目的product列。现在,我们将在
product共享列上执行内部合并并打印结果。 -
运行下一个单元格以执行内部合并:
![Explore data preprocessing tools and methods]()
请注意,如何仅包括共享项红色衬衫和白色连衣裙。 为了包括两个表中的所有条目,我们可以做一个外部合并。 让我们现在开始。
-
运行下一个单元格以执行外部合并:
![Explore data preprocessing tools and methods]()
这将从每个表返回所有数据,其中缺失值已用
NaN标记。 -
运行下一个单元格以执行外部合并:
![Explore data preprocessing tools and methods]()
这将从每个表返回所有数据,其中缺失值已用
NaN标记。
由于是我们在书中第一次遇到NaN值,因此现在是讨论这些在 Python 中如何工作的好时机。
首先,您可以通过执行a = float('nan')来定义NaN变量。 但是,如果要测试是否相等,则不能简单地使用标准比较方法。 最好使用 NumPy 之类的库中的高级函数来执行此操作。 下面的代码对此进行了说明:

这些结果中的某些似乎违反直觉。 但是,此行为背后有逻辑,并且要更深入地了解标准比较返回False的基本原因,请查看以下出色的 StackOverflow 线程。
-
您可能已经注意到,我们最近合并的表在前几行中有重复的数据。 让我们看看如何处理。
运行包含
df.drop_duplicates()的单元格以返回没有重复行的DataFrame版本:![Explore data preprocessing tools and methods]()
这是删除重复行的最简单和“标准”的方式。 要将这些更改应用到
df,我们可以设置inplace=True或执行类似df = df.drop_duplicated()的操作。 让我们看看另一种方法,该方法使用遮罩选择或删除重复的行。 -
运行包含
df.duplicated()的单元以打印True/False序列,标记重复的行:![Explore data preprocessing tools and methods]()
我们可以将此结果的总和确定有多少行重复,也可以将其用作选择重复行的掩码。
-
通过运行下面的两个单元来执行此操作:
![Explore data preprocessing tools and methods]()
-
我们可以使用简单的代字号(
~)来计算掩码的相反值,以提取去重复的DataFrame。 运行以下代码,并确信输出与df.drop_duplicates()中的输出相同:df[~df.duplicated()]![Explore data preprocessing tools and methods]()
-
这也可以用于从完整
DataFrame的子集中删除重复项。 例如,运行包含以下代码的单元格:df[~df['product'].duplicated()]![Explore data preprocessing tools and methods]()
在这里,我们正在做以下事情:
- 为产品行创建遮罩(真假序列),其中重复项用
True标记 - 使用波浪号(
~)与该掩码相反,以便将重复项标记为False,其他所有项均为True - 使用该掩码过滤掉
df的False行,它们对应于重复的乘积
不出所料,我们现在只剩下第一行红色衬衫行,因为已删除了重复的产品行。
为了继续执行这些步骤,让我们将
df替换为自身的重复数据删除版本。 这可以通过运行drop_duplicates并传递参数inplace=True来完成。 - 为产品行创建遮罩(真假序列),其中重复项用
-
通过运行包含以下代码的单元,对
DataFrame进行重复数据删除并保存结果:df.drop_duplicates(inplace=True)继续其他预处理方法,让我们忽略重复的行,并首先处理丢失的数据。 这是必需的,因为无法针对不完整的样本训练模型。 以蓝色裤子和白色燕尾服的缺失价格数据为例,让我们展示一些用于处理
NaN值的选项。 -
一种选择是删除行,如果您的
NaN样本缺少大多数值,这可能是个好主意。 通过运行包含df.dropna()的单元格来做到这一点:![Explore data preprocessing tools and methods]()
-
如果某个特征缺少大多数值,则最好完全删除该列。 通过运行包含与以前相同方法的单元格来执行此操作,但是这次传递的
axis参数指示列而不是行:![Explore data preprocessing tools and methods]()
通常,简单地降低
NaN值通常不是最佳选择,因为丢失数据永远不会是一件好事,尤其是在仅丢失一小部分采样值的时。 Pandas 提供了一种以多种不同方式填充NaN条目的方法,我们将在其中举例说明。 -
运行包含
df.fillna?的单元格以为 Pandas 的NaN-fill方法打印文档字符串:![Explore data preprocessing tools and methods]()
注意
value参数的选项; 例如,这可以是单个值或基于索引的字典/序列类型映射。 或者,我们可以将值保留为None并通过fill方法。 在本课中,我们将看到每个的示例。 -
通过运行包含以下代码的单元格,用平均产品价格填写缺失的数据:
```py
df.fillna(value=df.price.mean())
```

- 现在,通过运行包含以下代码的单元格,使用
fillna方法填充丢失的数据:
```py
df.fillna(method='pad')
```

请注意,**白色连衣裙**的价格是如何用来填补其下方缺失值的。
总结本节,我们将准备简单表以训练机器学习算法。 不用担心,我们实际上不会尝试在如此小的数据集上训练任何模型! 我们通过编码分类数据的类标签开始此过程。
- 在对标签进行编码之前,请运行“构建训练数据集”部分中的第一个单元格,以添加另一列代表平均产品评分的数据:

假设我们要使用此表来训练预测模型,我们首先应该考虑将所有变量更改为数值类型。
- 最简单的列是布尔列表:
in_stock。 在将其用于训练预测模型之前,应将其更改为数值,例如0和1。 这可以通过许多方式来完成,例如,通过运行包含以下代码的单元格:
```py
df.in_stock = df.in_stock.map({False: 0, True: 1})
```

- 编码函数的另一个选项是 Scikit-learn 的
LabelEncoder,可用于将类标签映射到更高级别的整数。 让我们通过运行包含以下代码的单元格进行测试:
```py
from sklearn.preprocessing import LabelEncoder
rating_encoder = LabelEncoder()
_df = df.copy()
_df.rating = rating_encoder.fit_transform(df.rating)
_df
```

在构建多项式模型时,这可能使我们想到在上一课中进行的预处理。 在这里,我们实例化一个标签编码器,然后使用`fit_transform`方法对其进行“训练”并“转换”我们的数据。 我们将结果应用于我们的`DataFrame``_df`的副本。
- 然后,可以通过运行
rating_encoder.inverse_transform(df.rating),使用我们使用变量rating_encoder引用的类将这些特征转换回去:

您可能会在这里注意到一个问题。 我们正在使用所谓的“常规”特征,其中标签具有固有顺序。 在这种情况下,我们应该期望等级`"low"`将被编码为 0,等级`"high"`将被编码为 2。但是,这不是我们看到的结果。 为了实现正确的序号标签编码,我们应该再次使用映射并自己构建字典。
- 通过运行包含以下代码的单元格来正确编码顺序标签:
```py
ordinal_map = {rating: index for index, rating in enumerate(['low', 'medium', 'high'])}
print(ordinal_map)
df.rating = df.rating.map(ordinal_map)
```

我们首先创建映射字典。 这是使用字典理解和枚举完成的,但是从结果来看,我们发现可以很容易地手动定义。 然后,就像前面对`in_stock`列所做的那样,我们将字典映射应用于特征。 查看结果,我们发现评级现在比以前更有意义,其中`low`标记为`0`,`medium`标记为`1`,`high`标记为`2`。
现在我们已经讨论了序数特征,让我们来谈谈另一种称为标称特征的类型。 这些是没有固有顺序的字段,在我们的案例中,我们看到`product`是一个完美的例子。
大多数 Scikit-learn 模型都可以在这样的数据上进行训练,在这种数据中,我们使用字符串而不是整数编码的标签。 在这种情况下,必须在引擎盖下进行必要的转换。 但是,对于 Scikit-learn 或其他机器学习和深度学习库中的所有模型而言,情况可能并非如此。 因此,优良作法是在预处理过程中自行编码。
- 将类标签从字符串转换为数值的一种常用技术称为“单热编码”。 这会将不同的类拆分为单独的特征。 可以用
pd.get_dummies()优雅地完成。 通过运行包含以下代码的单元格来执行此操作:
```py
df = pd.get_dummies(df)
```
最终的`DataFrame`如下所示:

在这里,我们看到了一次热编码的结果:`product`列已分为 4,每个唯一值一个。 在每一列中,我们找到`1`或`0`来代表该行是否包含特定值或乘积。
继续前进并忽略任何数据缩放(通常应完成),最后一步是将数据分为训练集和测试集,以用于机器学习。 这可以使用 Scikit-learn 的`train_test_split`完成。 假定其他特征值,我们假设我们将尝试预测某物品是否有库存。
- 通过运行包含以下代码的单元,将数据分为训练集和测试集:
```py
features = ['price', 'rating', 'product_blue pants',
'product_red shirt', 'product_white dress',
'product_white tuxedo']
X = df[features].values
target = 'in_stock'
y = df[target].values
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = \
train_test_split(X, y, test_size=0.3)
```

在这里,我们正在选择数据的子集,并将其输入到`train_test_split`函数中。 此函数具有四个输出,这些输出被打包到特征(`X`)和目标(`y`)的训练和测试单元中。
观察输出数据的形状,其中测试集大约有 30% 的样本,训练集大约有 70% 的样本。
在准备用于训练预测模型的实际数据时,我们将在以后看到类似的代码块。
到此结束了有关在机器学习应用中使用的清洗数据的部分。 让我们花一点时间来说明我们的 Jupyter 笔记本在测试各种数据转换方法以及最终记录我们决定的管道方面的有效性。 通过在处理之前仅更改特定的代码单元,可以很容易地将应用于数据的更新版本。 另外,如果我们希望对处理进行任何更改,则可以在笔记本中轻松测试这些更改,并且可以更改特定的单元以适应更改。 实现此目的的最佳方法可能是将笔记本复制到新文件中,以便我们始终可以保留原始分析的副本以供参考。
继续进行一项活动,我们现在将本节中的概念应用于大型数据集,以准备用于训练预测模型。
活动 A:准备为员工保留问题训练预测模型
假设您被雇用为一家公司寻求自由职业,该公司希望找到有关其雇员离职原因的见解。 他们收集了一套他们认为在这方面将有所帮助的数据。 它包括有关员工满意度,评估,工作时间,部门和薪水的详细信息。
该公司通过向您发送一个名为hr_data.csv的文件并询问您认为可以做什么来帮助阻止员工离职,与您共享数据。
要将迄今学到的概念应用于现实生活中的问题。 我们尤其希望:
- 根据可用数据,确定使用预测分析提供有影响力的业务见解的计划。
- 准备用于机器学习模型的数据。
注意
从本活动的开始,并继续本课程的其余部分,我们将使用人力资源分析,它是一个 Kaggle 数据集。
我们在本书中使用的数据集与在线版本之间存在细微的差异。 我们的人力资源分析数据包含一些NaN值。 这些是从数据集的在线版本中手动删除的,目的是说明数据清理技术。 出于相同的目的,我们还添加了称为is_smoker的数据列。
-
打开
lesson-2-workbook.ipynb笔记本文件,滚动到Activity A部分。 -
通过运行以下代码来检查表的标题:
%%bash head ../data/hr-analytics/hr_data.csv从输出来看,使自己确信它看起来是标准 CSV 格式。 对于 CSV 文件,我们应该能够简单地使用
pd.read_csv加载数据。 -
通过运行
df = pd.read_csv('../data/hr-analytics/hr_data.csv')将数据加载到 Pandas 中。 使用制表符补全可帮助键入文件路径。 -
通过打印
df.columns检查列,并通过使用df.head()和df.tail()打印DataFramehead和tail来确保数据已按预期加载:![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
我们可以看到它似乎已正确加载。 根据
tail索引值,大约有 15,000 行; 确保我们没有错过任何机会。 -
使用以下代码检查 CSV 文件中的行数(包括标题):
with open('../data/hr-analytics/hr_data.csv') as f: print(len(f.read().splitlines()))![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
-
将此结果与
len(df)进行比较,以确保我们已加载所有数据:![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
现在已经正确加载了客户的数据,让我们考虑一下如何使用预测分析来找到有关其员工离职原因的见解。
让我们完成创建预测分析计划的第一步:
- 查看可用数据:我们已经通过查看列,数据类型和样本数量来完成此操作
- 确定业务需求:客户明确表达了他们的需求:减少离职员工的数量
- 评估数据的适用性:鉴于提供的数据,让我们尝试确定可以帮助满足客户需求的计划
回想一下,如前所述,有效的分析技术可以产生有影响力的业务决策。 考虑到这一点,如果我们能够预测员工离职的可能性,那么业务可以有选择地针对这些员工进行特殊待遇。 例如,可以提高他们的薪水或减少他们的项目数量。 此外,可以使用模型估计这些变化的影响!
为了评估该计划的有效性,让我们考虑一下我们的数据。 每行代表在公司工作或已离职的雇员,如名为剩余的列所标记。 因此,给定一组特征,我们可以训练一个模型来预测此目标。
评估目标变量。 通过运行以下代码来检查丢失条目的分布和数量:
df.left.value_counts().plot('barh')
print(df.left.isnull().sum())

这是第二行代码的输出:

大约四分之三的样本是尚未离开的员工。 离开的小组构成了样本的其他四分之一。 这告诉我们我们正在处理一个不平衡的分类问题,这意味着我们在计算精度时必须采取特殊措施来考虑每个类别。 我们还看到没有目标变量丢失(没有NaN值)。
现在,我们将评估这些特征:
-
通过执行
df.dtypes打印每个数据类型。 观察我们如何结合使用连续和离散特征:![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
-
通过运行以下代码来显示特征部件分布:
for f in df.columns:try:fig = plt.figure()… … print('-'*30)
注意
有关完整的代码,请参考Lesson 2文件夹中的Lesson 2.txt文件。
该代码段有些复杂,但是对于显示数据集中的连续特征和离散特征的概述非常有用。 本质上,它假定每个特征都是连续的,并尝试绘制其分布,如果该特征是离散的,则还原为简单地绘制值计数。
结果如下:



对于许多特征,我们看到可能值的分布范围很广,表明特征空间中的变化很大。 这令人鼓舞; 在的较小范围内强烈分组的特征可能对模型不是很有帮助。 promotion_last_5years就是这种情况,我们看到绝大多数样本为 0。
我们需要做的下一步是从数据集中删除所有NaN值。
-
通过运行以下代码,检查每列中有多少个
NaN值:df.isnull().sum() / len(df) * 100![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
我们可以看到
average_montly_hours丢失了约 2.5%,time_spend_company丢失了 1%,is_smoker丢失了 98%! 让我们使用已经学到的几种不同策略来处理这些策略。 -
由于
is_smoker指标中几乎没有任何信息,因此让我们删除此列。 通过运行以下命令来执行此操作:del df['is_smoker']。 -
由于
time_spend_company是一个整数字段,因此我们将使用中位数来填充此列中的NaN值。 可以使用以下代码完成此操作:fill_value = df.time_spend_company.median() df.time_spend_company = df.time_spend_company.fillna(fill_value)最后要处理的列是
average_montly_hours。 我们可以做类似的事情,并使用中位数或四舍五入的平均值作为整数填充值。 但是,让我们尝试利用其与另一个变量的关系。 这可以使我们更准确地填充丢失的数据。 -
制作一个由
number_project分段的average_montly_hours箱形图。 这可以通过运行以下代码来完成:sns.boxplot(x='number_project', y='average_montly_hours', data=df)![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
我们可以看到项目数量与
average_monthly_hours之间的关系,这一结果不足为奇。 我们将通过不同地填充average_montly_hours的NaN值来利用这种关系,具体取决于该样本的项目数量。 具体来说,我们将使用每组的平均值。 -
通过运行以下代码来计算每个组的平均值:
mean_per_project = df.groupby('number_project')\ .average_montly_hours.mean() mean_per_project = dict(mean_per_project) print(mean_per_project)![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
然后,我们可以将其映射到
number_project列,并将生成的序列对象作为参数传递给fillna。 -
通过执行以下代码,将
average_montly_hours中的NaN值填充:fill_values = df.number_project.map(mean_per_project) df.average_montly_hours = df.average_montly_hours.fillna(fill_values) -
通过运行以下断言测试,确认
df不再具有NaN值。 如果它没有引发错误,则说明您已成功从表中删除了NaN:assert df.isnull().sum().sum() == 0 -
最后,我们将字符串和布尔值字段转换为整数表示形式。 特别是,我们将手动将目标变量
left从yes和no转换为1和0,并构建单热编码的特征。 通过运行以下代码来执行此操作:df.left = df.left.map({'no': 0, 'yes': 1}) df = pd.get_dummies(df) -
打印
df.columns以显示字段:![Activity A: Preparing to Train a Predictive Model for the Employee-Retention Problem]()
我们可以看到
department和salary被拆分成各种二进制特征。为机器学习准备数据的最后一步是缩放特征,但是由于各种原因(例如,某些模型不需要缩放),我们将在下一个活动中将其作为模型训练工作流的一部分进行。
-
我们已经完成了数据预处理,并准备继续进行训练模型! 让我们通过运行以下代码来保存预处理的数据:
```py
df.to_csv('../data/hr-analytics/hr_data_processed.csv', index=False)
```
同样,我们在此处暂停,以注意 Jupyter 笔记本在执行此初始数据分析和清理时满足我们的需求的程度。 想象一下,例如,我们将这个项目保留了几个月的时间。 回到它之后,我们可能不记得刚离开它时到底发生了什么。 不过,回头看这个笔记本,我们将能够追溯我们的步骤,并迅速回忆起我们先前所学到的有关数据的知识。 此外,我们可以使用任何新数据更新数据源,然后重新运行笔记本以准备用于我们的机器学习算法的新数据集。 回想一下在这种情况下,最好先制作一个笔记本副本,以免丢失初始分析结果。
总而言之,我们已经学习并应用了准备训练机器学习模型的方法。 我们首先讨论了确定可通过预测分析解决的问题的步骤。 这包括:
- 查看可用数据
- 确定业务需求
- 评估数据的适用性
我们还讨论了如何识别监督与非监督以及回归与分类问题。
找出问题所在后,我们学习了使用 Jupyter 笔记本构建和测试数据转换管道的技术。 这些技术包括用于填充缺失数据,转换分类特征以及构建训练/测试数据集的方法和最佳实践。
在本课程的其余部分中,我们将使用预处理后的数据来训练各种分类模型。 为了避免盲目地应用我们不了解的算法,我们首先介绍它们并概述它们的工作方式。 然后,我们使用 Jupyter 来训练和比较其预测能力。 在这里,我们有机会讨论机器学习中的更多高级主题,例如过拟合,K 折交叉验证和验证曲线。
训练分类模型
正如我们在上一课中已经看到的那样,使用诸如 Scikit-learn 之类的库和诸如 Jupyter 之类的平台,可以仅用几行代码来训练预测模型。 通过抽象化与优化模型参数有关的困难计算,可以做到这一点。 换句话说,我们处理一个黑盒,内部操作被隐藏。 这种简单性也带来了滥用算法的危险,例如在训练过程中过拟合或无法正确测试看不见的数据。 我们将展示如何在训练分类模型时避免这些陷阱,并通过使用 K 折交叉验证和验证曲线来产生可信赖的结果。
子主题 A:分类算法简介
回想一下监督机器学习的两种类型:回归和分类。 在回归中,我们预测一个连续的目标变量。 例如,回顾第一课的线性和多项式模型。 在本课程中,我们将重点介绍另一种类型的监督式机器学习:分类。 在这里,目标是使用可用指标来预测样本的类别。
在最简单的情况下,只有两个可能的类,这意味着我们正在执行二分类。 本课中的示例问题就是这种情况,我们尝试预测员工是否离开。 如果我们有两个以上的类标签,则我们正在进行多类分类。
尽管在使用 Scikit-learn 训练模型时,二分类和多类分类之间几乎没有区别,但是在“黑匣子”内部进行的操作却明显不同。 特别是,多类别分类模型通常使用“一对多”方法。 对于具有三个类别标签的情况,其工作原理如下。 当模型与数据“拟合”时,将训练三个模型,并且每个模型都会预测样本是属于单个类别还是属于某个其他类别。 这可能使我们想到了以前对特征进行的单热编码。 当对样本进行预测时,将返回具有最高置信度的类别标签。
在本课程中,我们将训练三种类型的分类模型:支持向量机,随机森林和 K 最近邻分类器。 这些算法中的每一个都有很大的不同。 但是,正如我们将看到的,由于 Scikit-learn,它们与训练和用于预测非常相似。 在切换到 Jupyter 笔记本并实现它们之前,我们将简要了解它们的工作原理。
SVM 试图找到最佳的超平面来划分类别。 这是通过最大化超平面和每个类别的最近样本(称为支持向量)之间的距离来完成的。
该线性方法还可用于使用核技巧对非线性类进行建模。 此方法将特征映射到确定超平面的更高维空间中。 我们一直在谈论的这个超平面也称为决策面,我们将在训练模型时对其进行可视化。
K 最近邻分类算法可存储训练数据并根据特征空间中的 K 个最近样本进行预测。 具有三个特征,可以将其可视化为预测样本周围的球体。 但是,我们经常要处理三个以上的特征,因此绘制超球面以找到最接近的 K 个样本。
随机森林是决策树的集合,其中每个决策树都已在训练数据的不同子集上进行了训练。
决策树算法基于一系列决策对样本进行分类。 例如,第一个决定可能是“如果特征x[1]小于或大于 0”。 然后,将在这种情况下拆分数据,并将其馈送到树的下降分支中。 决策树中的每个步骤都是基于最大程度地提高信息增益的特征拆分来决定的。
本质上,该术语描述了试图选择目标变量的最佳分割的数学。
训练随机森林包括为一组决策树创建引导数据集(即带替换的随机采样数据)数据集。 然后根据多数表决做出预测。 这些具有减少过拟合和更好地泛化的好处。
注意
决策树可用于对连续和分类数据的混合进行建模,这使它们非常有用。 此外,正如我们将在本课程后面看到的那样,可以限制树的深度以减少过拟合。 要详细了解决策树算法,请查看以下流行的 StackOverflow 答案。
作者在那里展示了一个简单的示例,并讨论了诸如节点纯度,信息增益和熵之类的概念。
使用 Scikit-learn 训练两特征分类模型
我们将继续解决第一个主题中介绍的员工保留问题。 我们先前准备了用于训练分类模型的数据集,在该数据集中我们预测了员工是否离开。 现在,我们将获取这些数据并将其用于训练分类模型:
-
如果您尚未这样做,请启动
NotebookApp并打开lesson-2-workbook.ipynb文件。 向下滚动到Topic B: Training classification models。 运行前几个单元格以设置默认图形尺寸,并将我们先前保存的处理后的数据加载到 CSV 文件中。对于此示例,我们将在两个连续的特征上训练分类模型:
satisfaction_level和last_evaluation。 -
通过使用以下代码运行单元格,绘制连续目标变量的双变量和单变量图:
sns.jointplot('satisfaction_level', 'last_evaluation', data=df, kind='hex')![Training two-feature classification models with scikit-learn]()
如上图所示,数据中有一些非常不同的模式。
-
通过运行包含以下代码的单元格,重新绘制双变量分布,对目标变量进行分段:
plot_args = dict(shade=True, shade_lowest=False) for i, c in zip((0, 1), ('Reds', 'Blues')): sns.kdeplot(df.loc[df.left==i, 'satisfaction_level'], df.loc[df.left==i, 'last_evaluation'], cmap=c, **plot_args)![Training two-feature classification models with scikit-learn]()
现在,我们可以看到模式与目标变量之间的关系。 在本节的其余部分,我们将尝试利用这些模式来训练有效的分类模型。
-
通过运行包含以下代码的单元,将数据分为训练集和测试集:
from sklearn.model_selection import train_test_splitfeatures = ['satisfaction_level', 'last_evaluation']X_train, X_test, y_train, y_test = train_test_split(df[features].values, df['left'].values, test_size=0.3, random_state=1)当对输入数据进行缩放以使所有特征都处于相同的顺序时,我们的前两个模型(支持向量机和 K 最近邻算法)最有效。 我们将使用 Scikit-learn 的
StandardScaler完成此操作。 -
加载
StandardScaler并创建一个新实例,如scaler变量所引用。 将洁牙机安装在训练集上并进行变换。 然后,变换测试集。 运行包含以下代码的单元格:from sklearn.preprocessing import StandardScalerscaler = StandardScaler() X_train_std = scaler.fit_transform(X_train) X_test_std = scaler.transform(X_test)注意
在进行机器学习时,一个容易犯的错误是使缩放器“适合”整个数据集,而实际上它仅应“适合”训练数据。 例如,在拆分为训练集和测试集之前缩放数据是错误的。 我们不希望这样做,因为模型训练不应受到测试数据的任何影响。
-
导入 Scikit-learn 支持向量机类,并通过运行包含以下代码的单元格将模型拟合到训练数据上:
from sklearn.svm import SVCsvm = SVC(kernel='linear', C=1, random_state=1) svm.fit(X_train_std, y_train)然后,我们训练线性 SVM 分类模型。
C参数控制分类错误的代价,从而可以控制模型的方差和偏差。 -
通过运行包含以下代码的单元格,对看不见的数据计算此模型的准确率:
from sklearn.metrics import accuracy_scorey_pred = svm.predict(X_test_std)acc = accuracy_score(y_test, y_pred)print('accuracy = {:.1f}%'.format(acc*100)) >> accuracy = 75.9%我们预测测试样本的目标,然后使用 Scikit-learn 的
accuracy_score函数确定准确率。 结果看起来很有希望,达到约 75%! 对于我们的第一个模型来说还不错。 但是请记住,目标是不平衡的。 让我们看看每个类别的预测有多准确。 -
计算混淆矩阵,然后通过运行包含以下代码的单元格来确定每个类别中的准确率:
from sklearn.metrics import confusion_matrixcmat = confusion_matrix(y_test, y_pred)scores = cmat.diagonal() / cmat.sum(axis=1) * 100print('left = 0 : {:.2f}%'.format(scores[0]))print('left = 1 : {:.2f}%'.format(scores[1])) >> left = 0 : 100.00% >> left = 1 : 0.00%看起来该模型只是将每个样本分类为
0,这显然根本没有帮助。 让我们使用等高线图显示特征空间中每个点的预测类。 这通常称为决策区域图。 -
使用来自
mlxtend库的有用函数来绘制决策区域。 运行包含以下代码的单元格:from mlxtend.plotting import plot_decision_regionsN_samples = 200X, y = X_train_std[:N_samples], y_train[:N_samples] plot_decision_regions(X, y, clf=svm)![Training two-feature classification models with scikit-learn]()
该函数绘制决策区域以及通过作为参数传递的一组样本。 为了正确查看决策区域而没有太多样本遮挡我们的视线,我们仅将测试数据的 200 个样本子集传递给
plot_decision_regions函数。 在这种情况下,当然没关系。 我们看到结果完全是红色的,表明特征空间中的每个点都将归为 0。线性模型不能很好地描述这些非线性模式,不足为奇。 回想一下,我们提到了使用 SVM 对非线性问题进行分类的核技巧。 让我们看看这样做是否可以改善结果。
-
通过运行包含 SVC 的单元,为 Scikit-learn 的 SVM 打印文档字符串。 向下滚动并查看参数说明。 注意
kernel选项,该选项实际上默认为rbf启用。 使用此kernel选项通过运行包含以下代码的单元来训练新的 SVM:
```py
svm = SVC(kernel='rbf', C=1, random_state=1)
svm.fit(X_train_std, y_train)
```
- 为了更轻松地评估此模型以及将来的模型表现,我们定义一个名为
check_model_fit的函数,该函数计算可用于比较模型的各种指标。 运行定义此函数的单元格。
在此示例中已经看到了在此函数中完成的每个计算; 它仅计算精度并绘制决策区域。
- 通过运行包含以下代码的单元,在训练数据上显示新训练的核-SVM 结果:
```py
check_model_fit(svm, X_test_std, y_test)
```


结果要好得多。 现在,我们可以捕获数据中的某些非线性模式,并正确地对大多数离职员工进行分类。
plot_decision_regions函数
plot_decision_regions函数由mlxtend提供,Sebastian Raschka 开发了 Python 库。 值得一看一下源代码(当然是用 Python 编写的)以了解如何绘制这些图。 确实不是太复杂。
在 Jupyter 笔记本中,使用from mlxtend.plotting import plot_decision_regions导入该函数,然后使用plot_decision_regions?拉起帮助,然后滚动到底部以查看本地文件路径:

然后,打开文件并签出! 例如,您可以在笔记本中运行cat:

可以,但是不理想,因为该代码没有颜色标记。 最好将其复制(这样就可以避免意外更改原件)并使用您喜欢的文本编辑器将其打开。
当提请注意负责映射决策区域的代码时,我们会看到跨越特征空间的数组X_predict上的预测Z轮廓图。

让我们继续下一个模型:K 最近邻居。
为模型训练 K 最近邻
-
加载 Scikit-learn KNN 分类模型并通过运行包含以下代码的单元格来打印文档字符串:
from sklearn.neighbors import KNeighborsClassifier KNeighborsClassifier?n_neighbors参数决定进行分类时要使用的样本数量。 如果权重参数设置为统一,则类别标签由多数表决决定。 权重的另一个有用选择是距离,距离越近的样本在投票中的权重越高。 像大多数模型参数一样,最佳选择取决于特定的数据集。 -
用
n_neighbors = 3训练 KNN 分类器,然后计算准确率和决策区域。 运行包含以下代码的单元格:knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train_std, y_train) check_model_fit(knn, X_test_std, y_test)![Training k-nearest neighbors fork-Nearest Neighborstraining our model]()
![Training k-nearest neighbors fork-Nearest Neighborstraining our model]()
特别是对于类别 1,我们看到了整体准确率的提高,并且有了显着提高。 但是,决策区域图将表明我们过拟合了数据。 坚硬的,“不稳定的”决策边界和到处都是蓝色的小口袋可以证明这一点。 我们可以通过增加最近邻居的数量来软化决策边界并减少过拟合。
-
通过运行包含以下代码的单元,使用
n_neighbors = 25训练 KNN 模型:knn = KNeighborsClassifier(n_neighbors=25)knn.fit(X_train_std, y_train) check_model_fit(knn, X_test_std, y_test)![Training k-nearest neighbors fork-Nearest Neighborstraining our model]()
![Training k-nearest neighbors fork-Nearest Neighborstraining our model]()
如我们所见,决策边界的波动性大大降低,蓝色的口袋也大大减少了。 1 类的准确率略低,但是我们需要使用更全面的方法(例如 K 折交叉验证)来确定两个模型之间是否存在显着差异。
请注意,增加
n_neighbors对训练时间没有影响,因为该模型只是存储数据。 但是,预测时间将受到很大影响。
注意
在使用现实数据进行机器学习时,重要的是算法必须足够快地运行以达到其目的。 例如,用于预测的明天超过一天运行时间的脚本完全没有用! 在处理大量数据时,还应考虑内存问题。
我们现在将训练一个随机森林。
训练随机森林
注意
观察在每个模型上进行训练和做出预测的相似之处,尽管每个内部都有很大差异。
-
训练由 50 个决策树组成的随机森林分类模型,每个决策树的最大深度为 5。 运行包含以下代码的单元格:
from sklearn.ensemble import RandomForestClassifierforest = RandomForestClassifier(n_estimators=50, max_depth=5,random_state=1) forest.fit(X_train, y_train) check_model_fit(forest, X_test, y_test)![Training a Random Forest]()
![Training a Random Forest]()
注意决策树机器学习算法产生的独特的轴平行决策边界。
我们可以访问用于构建随机森林的任何单个决策树。 这些树存储在模型的
estimators_attribute中。 让我们绘制这些决策树之一,以了解正在发生的事情。 执行此需要 graphviz 依赖项,有时可能很难安装。 -
通过运行包含以下代码的单元格,在 Jupyter 笔记本中绘制决策树之一:
from sklearn.tree import export_graphvizimport graphvizdot_data = export_graphviz(forest.estimators_[0],out_file=None, feature_names=features, class_names=['no', 'yes'], filled=True, rounded=True, special_characters=True)graph = graphviz.Source(dot_data) graph![Training a Random Forest]()
我们可以看到,由于设置了
max_depth=5,每个路径限制为五个节点。 橙色框代表否(尚未离开公司)的预测,蓝色框代表是(已离开公司)。 每个框的阴影(浅色,深色等)表示置信度,与gini值相关。
总而言之,我们已经完成了本节中的两个学习目标:
- 我们对支持向量机(SVM),K 最近邻分类器(kNN)和随机森林有了定性的了解
- 现在,我们可以使用 Scikit-learn 和 Jupyter 笔记本训练各种模型,因此我们可以放心地建立和比较预测模型
特别是,我们使用了来自员工保留问题的预处理数据来训练分类模型,以预测员工是否已离开公司。 为了使事情简单并着重于算法,我们建立了模型来预测这一点,仅给出两个特征:满意度和最后评估值。 这个二维特征空间还使我们能够可视化决策边界并确定过拟合的外观。
在以下部分中,我们将介绍机器学习中的两个重要主题:K 折交叉验证和验证曲线。
子主题 B:使用 K 折交叉验证和验证曲线来评估模型
到目前为止,我们已经在数据的子集上训练了模型,然后在看不见的部分(称为测试集)上评估了表现。 这是一个好习惯,因为训练数据的模型表现不能很好地表明其作为预测变量的有效性。 通过过拟合模型来提高训练数据集的准确率非常容易,这可能会导致看不见的数据表现下降。
就是说,仅这样的数据分割训练模型还不够好。 数据中存在自然差异,这会导致准确率根据训练和测试分裂而有所不同(甚至略有不同)。 此外,仅使用一个训练/测试组来比较模型会导致对某些模型的偏见并导致过拟合。
K 折交叉验证为提供了解决此问题的方法,并允许通过每次准确率计算中的误差估计来解决方差。 反过来,这自然会导致使用验证曲线来调整模型参数。 这些将精度绘制为超参数的函数,例如随机森林中使用的决策树数或最大深度。
注意
这是我们第一次使用术语超参数。 它引用初始化模型时定义的参数,例如 SVM 的C参数。 这与训练后的模型的参数(例如训练后的 SVM 的决策边界超平面的方程式)相反。
下图说明了该方法,其中我们看到了如何从数据集中选择 K 折:

K 折交叉验证算法如下:
- 将数据拆分为大小近似相等的
k个“折叠”。 - 在不同的折叠组合上测试并训练
k模型。 每个模型将包括k-1折叠训练数据,剩余的折叠用于测试。 在这种方法中,每个折叠最终仅被用作一次验证数据。 - 通过取
k值的平均值来计算模型精度。 还计算标准差以在值上提供误差线。
标准设置为k = 10,但如果使用大数据集,则应考虑使用k较小的值。
此验证方法可用于可靠地比较具有不同超参数(例如,SVM 的C参数或 KNN 分类器中最近邻居的数量)的模型表现。 它也适合比较完全不同的模型。
一旦确定了最佳模型,应在整个数据集上对其进行重新训练,然后再用于预测实际分类。
当使用 Scikit-learn 实现此功能时,通常会使用略微改进的普通 K 折算法的变体来代替。 这被称为分层 K 折。 的改进之处在于,分层的 K 折交叉验证可在折叠中大致维持类标签种群。 可以想象,这减少了模型的整体方差,并降低了高度不平衡的模型引起偏差的可能性。
验证曲线是训练和验证指标作为某些模型参数的函数的图。 它们使我们能够进行良好的模型参数选择。 在本书中,我们将使用准确率得分作为这些图的度量。
注意
考虑以下验证曲线,在该曲线中,准确率得分是根据伽玛 SVM 参数绘制的:

从图的左侧开始,我们可以看到两组数据在得分上都一致,这很好。 但是,与其他伽玛值相比,该分数也很低,因此我们说该模型不适合数据。 随着伽马的增加,我们可以看到一点,这两条线的误差线不再重叠。 从这一点开始,我们看到分类器过拟合数据,因为与验证集相比,模型在训练集上的表现越来越好。 可以通过在两行误差线重叠的情况下寻找较高的验证分数来找到伽玛参数的最佳值。
请记住,某些参数的学习曲线仅在其他参数保持不变时才有效。 例如,如果在该图中训练 SVM,我们可以决定选择 10-4 数量级的伽玛。 但是,我们可能还想优化C参数。 如果C的值不同,则前面的图将不同,并且我们对 Gamma 的选择可能不再是最佳的。
在 Scikit-learn 和 Python 中使用 K 折交叉验证和验证曲线
-
如果您尚未这样做,请启动
NotebookApp并打开lesson-2-workbook.ipynb文件。 向下滚动至Subtopic B: K-fold cross-validation and validation curves。训练数据应该已经在笔记本的内存中,但是让我们重新加载它以提醒我们正在使用什么。
-
加载数据并为训练/验证集选择
satisfaction_level和last_evaluation函数。 这次我们将不使用训练测试拆分,因为我们将改用 K 折验证。 运行包含以下代码的单元格:df = pd.read_csv('../data/hr-analytics/hr_data_processed.csv') features = ['satisfaction_level', 'last_evaluation'] X = df[features].values y = df.left.values -
通过运行包含以下代码的单元来实例化随机森林模型:
clf = RandomForestClassifier(n_estimators=100, max_depth=5) -
为了使用分层的 K 折交叉验证来训练模型,我们将使用
model_selection.cross_val_score函数。使用分层 K 折检验训练我们模型
clf的 10 个变体。 请注意,默认情况下,Scikit-learn 的cross_val_score会进行这种类型的验证。 运行包含以下代码的单元格:from sklearn.model_selection import cross_val_score np.random.seed(1) scores = cross_val_score( estimator=clf, X=X, y=y, cv=10) print('accuracy = {:.3f} +/- {:.3f}'.format(scores.mean(), scores. std())) >> accuracy = 0.923 +/- 0.005注意我们如何使用
np.random.seed设置随机数生成器的种子,因此确保了有关随机林中每个折叠和决策树的随机选择样本的可重复性。 -
使用这种方法,我们将准确率计算为每折的平均值。 我们还可以通过打印分数来查看每折的个人准确率。 要查看这些内容,
print(scores):>> array([ 0.93404397, 0.91533333, 0.92266667, 0.91866667, 0.92133333, 0.92866667, 0.91933333, 0.92 , 0.92795197, 0.92128085])使用
cross_val_score非常方便,但是并不能告诉我们每个类的精度。 我们可以使用model_selection.StratifiedKFold类手动完成此操作。 此类将折叠数作为初始化参数,然后使用split方法为数据构建随机采样的“掩码”。 掩码只是一个包含另一个数组中项目索引的数组,然后可以通过执行以下操作返回项目:data[mask]。 -
定义一个自定义类,以计算 K 折交叉验证类的准确率。 运行包含以下代码的单元格:
from sklearn.model_selection import StratifiedKFold… … print('fold: {:d} accuracy: {:s}'.format(k+1, str(class_acc))) return class_accuracy注意
有关完整的代码,请参考
Lesson 2文件夹中的Lesson 2.txt文件。 -
然后,我们可以使用与步骤 4 非常相似的代码来计算类的准确率。 通过运行包含以下代码的单元格来执行此操作:
from sklearn.model_selection import cross_val_scorenp.random.seed(1)… … >> fold: 10 accuracy: [ 0.98861646 0.70588235] >> accuracy = [ 0.98722476 0.71715647] +/- [ 0.00330026 0.02326823]注意
有关完整的代码,请参考
Lesson 2文件夹中的Lesson 2.txt文件。现在我们可以看到每折的类别准确率! 很整洁吧?
-
让我们继续展示如何使用
model_selection.validation_curve来计算验证曲线。 此函数使用分层的 K 折交叉验证来训练给定参数各种值的模型。通过在
max_depth值范围内训练随机森林,进行绘制验证曲线所需的计算。 运行包含以下代码的单元格:clf = RandomForestClassifier(n_estimators=10) max_depths = np.arange(3, 16, 3) train_scores, test_scores = validation_curve( estimator=clf, X=X, y=y, param_name='max_depth', param_range=max_depths, cv=10);这将返回具有每个模型的交叉验证分数的数组,其中模型具有不同的最大深度。 为了使结果可视化,我们将利用 Scikit-learn 文档中提供的功能。
-
运行定义了
plot_validation_curve的单元格。 然后,运行包含以下代码的单元格以绘制图:plot_validation_curve(train_scores, test_scores, max_depths, xlabel='max_depth')![Using k-fold cross validation and validation curves in Python with scikit-learn]()
回想一下如何为决策树设置最大深度来限制过拟合的数量? 这反映在验证曲线中,在该曲线中,我们看到右侧的较大最大深度值发生过拟合。
max_depth的一个好值似乎是6,在这里我们看到了训练和验证的准确率。 当max_depth等于3时,由于训练和验证精度较低,我们看到模型不适合数据。
总而言之,我们已经学习并实现了两种重要的技术来构建可靠的预测模型。 第一种这样的技术是 K 折交叉验证,该技术用于将数据拆分为各种训练/测试批次并产生设定的准确率。 然后,从这个集合中,我们计算出平均准确率和标准差作为误差的度量。 这很重要,因此我们可以衡量模型的可变性,并可以产生可信赖的准确率。
我们还了解了另一种可以确保获得值得信赖的结果的技术:验证曲线。 这些使我们可以根据比较训练和验证准确率来可视化模型何时过拟合。 通过在选定的超参数的范围内绘制曲线,我们可以确定其最佳值。
在本课程的最后部分,我们将到目前为止所学到的所有知识综合起来,以便为员工保留问题建立最终的预测模型。 与迄今为止训练的模型相比,我们力求通过将模型中数据集的所有特征包括在内来提高准确率。 我们将看到现在熟悉的主题,例如 K 折交叉验证和验证曲线,但是我们还将引入一些新的东西:降维技术。
子主题 C:降维技术
降维可以仅涉及从训练数据中删除不重要的特征,但是存在更多奇特的方法,例如主成分分析(PCA)和线性判别分析(LDA)。 这些技术允许进行数据压缩,其中可以将仅来自大量特征的最重要信息编码为几个特征。
在本子主题中,我们将重点介绍 PCA。 通过将投影到正交的“主成分”的新子空间中,该技术将其转换为数据,其中具有最高特征值的成分编码用于训练模型的最多信息。 然后,我们可以简单地选择一些主要成分来代替原始的高维数据集。 例如,PCA 可用于编码图像中每个像素的信息。 在这种情况下,原始特征空间的尺寸将等于图像中像素的数量。 然后,可以使用 PCA 来减少此高维空间,在该空间中,用于训练预测模型的大多数有用信息可能会减少到仅几个维。 这不仅可以节省训练和使用模型时的时间,还可以通过消除数据集中的噪声使它们表现更好。
就像我们已经看到的模型一样,没有必要对 PCA 进行详细的了解以利用这些好处。 但是,我们将进一步深入探讨 PCA 的技术细节,以便更好地对其进行概念化。 PCA 的关键见解是基于相关性识别特征之间的模式,因此 PCA 算法计算协方差矩阵,然后将其分解为特征向量和特征值。 然后,将向量用于将数据转换为新的子空间,从中可以选择固定数量的主分量。
在以下部分中,我们将看到一个示例,该示例说明了如何使用 PCA 来改善我们正在研究的员工保留问题的随机森林模型。 这是在整个特征空间上训练分类模型之后完成的,以了解降维如何影响我们的精度。
为员工保留问题训练预测模型
我们已经花费了大量的精力来计划机器学习策略,预处理数据以及为员工保留问题建立预测模型。 回想一下,我们的业务目标是帮助客户防止员工离职。 我们决定采取的策略是建立一个分类模型,该模型可以预测员工离职的可能性。 这样,公司可以评估当前员工离职的可能性,并采取措施防止这种情况的发生。
根据我们的策略,我们可以总结出如下的预测模型类型:
- 在标记的训练数据上进行有监督的学习
- 具有两个类别标签的分类问题(二进制)
尤其是,我们正在训练模型,以根据一系列连续和明确的特征来确定员工是否已离开公司。 在“活动 A”和“训练员工保留问题的预测模型”中准备了用于机器学习的数据之后,我们接着实现了 SVM,K 最近邻和随机森林算法仅使用两个特征。 这些模型能够以超过 90% 的总体准确率进行预测。 但是,在查看特定类别的准确率时,我们发现离职的员工(class-label 1)只能以 70-80% 的准确率进行预测。 让我们看看可以通过利用全部特征空间将改进多少。
-
在
lesson-2-workbook.ipynb笔记本中,向下滚动到该部分的代码。 我们应该已经从上一节中加载了预处理的数据,但是如果需要,可以通过执行df = pd.read_csv('../data/hr-analytics/hr_data_processed.csv')再次完成。 然后,使用print(df.columns)打印DataFrame列。 -
定义所有特征的列表,方法是将
df.columns中的输出复制并粘贴到新列表中(确保删除目标变量left)。 然后,像之前一样定义X和Y。 如下所示:features = ['satisfaction_level', 'last_evaluation', 'number_project','average_montly_hours', 'time_spend_company', 'work_accident',… … X = df[features].values y = df.left.values注意
有关完整的代码,请参考
Lesson 2文件夹中的Lesson 2.txt文件。查看特征名称,回想一下每个特征的值。 向上滚动到我们在第一个活动中创建的直方图集,以帮助您慢走记忆。 前两个特征是连续的。 这些是我们在前两个练习中用于训练模型的内容。 之后,我们有一些离散的特征,例如
number_project和time_spend_company,然后是一些二进制字段,例如work_accident和promotion_last_5years。 我们还具有一堆二进制特征,例如department_IT和department_accounting,它们是通过单热编码创建的。考虑到类似的特征,随机森林是一种非常有吸引力的模型。 一方面,它们与由连续数据和分类数据组成的特征集兼容,但这并不是特别的; 例如,也可以对 SVM 进行混合特征类型的训练(只要进行适当的预处理)。
注意
如果您有兴趣在混合类型输入特征上训练 SVM 或 K 最近邻分类器,则可以使用此 StackExchange 答案中的数据缩放处方。
一种简单的方法是如下预处理数据:
- 标准化连续变量
- 单热编码分类特征
- 将二进制值从
0和1移至-1和1 - 然后,可以使用混合特征数据来训练各种分类模型
-
我们需要为随机森林模型找出最佳参数。 让我们从使用验证曲线调整
max_depth超参数开始。 通过运行以下代码来计算训练和验证的准确率:%%time np.random.seed(1) clf = RandomForestClassifier(n_estimators=20) max_depths = [3, 4, 5, 6, 7, 9, 12, 15, 18, 21] train_scores, test_scores = validation_curve( estimator=clf, X=X, y=y, param_name='max_depth', param_range=max_depths, cv=5);我们正在测试 10 个具有 K 折交叉验证的模型。 通过设置
k = 5,我们从得出每个模型的准确率的五个估计,我们从中提取平均值和标准差以绘制在验证曲线中。 我们总共训练了 50 个模型,由于n_estimators设置为 20,所以我们总共训练了 1,000 个决策树! 大约需要 10 秒钟! -
使用上一个练习中的自定义
plot_validation_curve函数绘制验证曲线。 运行以下代码:plot_validation_curve(train_scores, test_scores, max_depths, xlabel='max_depth');![Training a predictive model for the employee retention problem]()
对于较小的最大深度,我们看到该模型无法拟合数据。 通过使决策树更深,并在数据中编码更复杂的模式,总精度会急剧增加。 随着最大深度的进一步增加和的准确率接近 100%,我们发现该模型过拟合了数据,导致训练和验证的准确率不断提高。 基于此图,让我们为模型选择
6的max_depth。我们确实应该对
n_estimators做同样的事情,但是出于节省时间的精神,我们将跳过它。 欢迎您自己绘制。 您应该在训练和验证集之间找到适用于各种值的协议。 通常,最好在“随机森林”中使用更多的决策树估计器,但这是以增加训练时间为代价的。 我们将使用 200 个估计器来训练我们的模型。 -
使用
cross_val_class_score(我们之前创建的按类进行 K 折交叉验证)来测试所选模型,即max_depth = 6和n_estimators = 200的随机森林:np.random.seed(1)clf = RandomForestClassifier(n_estimators=200, max_depth=6)scores = cross_val_class_score(clf, X, y)print('accuracy = {} +/- {}'\.format(scores.mean(axis=0), scores.std(axis=0))) >> accuracy = [ 0.99553722 0.85577359] +/- [ 0.00172575 0.02614334]与以前只有两个连续特征的情况相比,现在使用完整特征集的准确率要高得多!
-
通过运行以下代码,用箱图可视化精度:
fig = plt.figure(figsize=(5, 7))sns.boxplot(data=pd.DataFrame(scores, columns=[0, 1]),palette=sns.color_palette('Set1'))plt.xlabel('Left') plt.ylabel('Accuracy')![Training a predictive model for the employee retention problem]()
随机森林可以提供特征表现的估计。
注意
Scikit-learn 中的特征重要性是根据节点杂质相对于每个特征的变化而计算的。 有关的详细说明,请查看以下 StackOverflow 线程,以了解如何在随机森林分类器中确定特征的重要性。
-
通过运行以下代码,绘制存储在属性
feature_importances_中的特征重要性:pd.Series(clf.feature_importances_, name='Feature importance',index=df[features].columns)\.sort_values()\.plot.barh() plt.xlabel('Feature importance')![Training a predictive model for the employee retention problem]()
从单热编码变量
department和salary的有用贡献看来,我们并没有从中获得太多。 同样,promotion_last_5years和work_accident函数似乎不太有用。让我们使用主成分分析(PCA)将所有这些弱特征浓缩为几个主要成分。
-
从 Scikit-learn 导入
PCA类并转换特征。 运行以下代码:from sklearn.decomposition import PCApca_features = \… … pca = PCA(n_components=3) X_pca = pca.fit_transform(X_reduce)注意
有关完整的代码,请参考
Lesson 2文件夹中的Lesson 2.txt文件。 -
通过单独键入
X_pca并执行单元格来查看它的字符串表示形式:>> array([[-0.67733089, 0.75837169, -0.10493685], >> [ 0.73616575, 0.77155888, -0.11046422], >> [ 0.73616575, 0.77155888, -0.11046422], >> ..., >> [-0.67157059, -0.3337546 , 0.70975452], >> [-0.67157059, -0.3337546 , 0.70975452], >> [-0.67157059, -0.3337546 , 0.70975452]])由于我们要求获得前三个分量,因此我们得到了三个向量。
-
使用以下代码将新特征添加到我们的
DataFrame中:
```py
df['first_principle_component'] = X_pca.T[0]df['second_principle_component'] = X_pca.T[1]
df['third_principle_component'] = X_pca.T[2]
```
选择我们的降维特征集来训练新的随机森林。 运行以下代码:
```py
features = ['satisfaction_level', 'number_project', 'time_spend_
company',
'average_montly_hours', 'last_evaluation',
'first_principle_component',
'second_principle_component',
'third_principle_component']
X = df[features].values
y = df.left.values
```
- 通过 K 折交叉验证来评估新模型的准确率。 可以通过运行与以前相同的代码来完成此操作,其中
X现在指向不同的特征。 代码如下:
```py
np.random.seed(1)
clf = RandomForestClassifier(n_estimators=200, max_depth=6)
scores = cross_val_class_score(clf, X, y)
print('accuracy = {} +/- {}'\
.format(scores.mean(axis=0), scores.std(axis=0)))
>> accuracy = [ 0.99562463 0.90618594] +/- [ 0.00166047 0.01363927]
```
- 使用箱形图以与以前相同的方式可视化结果。 代码如下:
```py
fig = plt.figure(figsize=(5, 7))sns.boxplot(data=pd.DataFrame(scores, columns=[0, 1]),
palette=sns.color_palette('Set1'))plt.xlabel('Left')
plt.ylabel('Accuracy')
```

将其与先前的结果进行比较,我们发现 1 级精度有所提高! 现在,大多数验证集返回的准确率都超过 90%。 可以将 90.6% 的平均精度与降低尺寸之前的的 85.6% 的精度进行比较!
让我们选择它作为我们的最终模型。 在生产中使用它之前,我们需要在整个样本空间上对其进行重新训练。
- 通过运行以下代码来训练最终的预测模型:
```py
np.random.seed(1)clf = RandomForestClassifier(n_estimators=200, max_depth=6)
clf.fit(X, y)
```
- 使用
externals.joblib.dump将训练后的模型保存到二进制文件中。 运行以下代码:
```py
from sklearn.externals import joblib
joblib.dump(clf, 'random-forest-trained.pkl')
```
- 例如,通过运行
!ls *.pkl,检查它是否已保存到工作目录中。 然后,通过运行以下代码来测试我们是否可以从文件中加载模型:
```py
clf = joblib.load('random-forest-trained.pkl')
```
恭喜你! 我们已经训练了最终的预测模型! 现在,让我们看一个示例,说明如何使用它为客户提供业务见解。
假设我们有一个特定的员工,我们将其称为 Sandra。 管理层注意到她正在非常努力地工作,并在最近的一项调查中报告了较低的工作满意度。 因此,他们想知道她辞职的可能性。
为了简单起见,让我们以她的特征值作为训练集中的样本(但请假装这是看不见的数据)。
- 通过运行以下代码列出 Sandra 的特征值:
```py
sandra = df.iloc[573]X = sandra[features]X
>> satisfaction_level 0.360000
>> number_project 2.000000
>> time_spend_company 3.000000
>> average_montly_hours 148.000000
>> last_evaluation 0.470000
>> first_principle_component 0.742801
>> second_principle_component -0.514568
>> third_principle_component -0.677421
```
下一步是询问模型认为她应该加入哪个小组。
- 通过运行以下代码来预测 Sandra 的类标签:
```py
clf.predict([X])
>> array([1])
```
模型将她归类为已经离开公司; 不是一个好兆头! 我们可以更进一步,计算每个类标签的概率。
- 使用
clf.predict_proba来预测我们的模型预测 Sandra 退出的可能性。 运行以下代码:
```py
clf.predict_proba([X])
>> array([[ 0.06576239, 0.93423761]])
```
我们看到该模型预测她辞职的准确率为 93%。
由于这显然是管理层的一个危险信号,他们决定制定一个计划,将她的每月工作时间减少到`100`,并将公司的时间减少到`1`。
- 使用 Sandra 的新计划指标来计算新概率。 运行以下代码:
```py
X.average_montly_hours = 100X.time_spend_company = 1clf.predict_proba([X])
>> array([[ 0.61070329, 0.38929671]])
```
出色的! 现在我们可以看到该模型返回她退出的可能性仅为 38%! 相反,它现在预测她不会离开公司。
我们的模型允许管理层做出数据驱动的决策。 通过减少她在公司的工作时间这一特定数额,该模型告诉我们,她很可能将继续在公司任职!
总结
在本课程中,我们看到了如何在 Jupyter 笔记本中训练预测模型。
首先,我们讨论了如何计划机器学习策略。 我们考虑了如何设计可带来切实可行的业务见解的计划,并强调了使用数据来帮助制定切实可行的业务目标的重要性。 我们还解释了机器学习术语,例如监督学习,无监督学习,分类和回归。
接下来,我们讨论了使用 Scikit-learn 和 Pandas 预处理数据的方法。 这包括冗长的讨论以及机器学习中非常耗时的部分的示例:处理丢失的数据。
在课程的后半部分,我们针对二进制问题训练了预测分类模型,比较了如何为各种模型(例如 SVM,K 最近邻和随机森林)绘制决策边界。 然后,我们展示了如何使用验证曲线来进行良好的参数选择以及降维如何改善模型表现。 最后,在活动结束时,我们探索了如何在实践中使用最终模型来制定数据驱动型决策。
三、Web 爬取和交互式可视化
到目前为止,在本书中,我们一直专注于使用 Jupyter 构建可重现的数据分析管道和预测模型。 在本课中,我们将继续探讨这些主题,但是这里的主要重点是数据采集。 特别是,我们将向您展示如何使用 HTTP 请求从网络上获取数据。 这将涉及通过请求和解析 HTML 来抓取网页。 然后,我们将使用交互式可视化技术来总结本课程,以探索我们收集的数据。
在线可用的数据量巨大,并且相对容易获取。 它也在不断增长,并且变得越来越重要。 这种持续增长的部分原因是全球不断从报纸,杂志和电视转向在线内容的结果。 借助可随时随地在手机上使用自定义新闻源以及 Facebook,Reddit,Twitter 和 YouTube 等实时新闻来源,很难想象历史上其他替代方法的使用时间会更长。 令人惊讶的是,这仅占在线可用数据量越来越大的一部分。
随着这种向使用 HTTP 服务(博客,新闻网站,Netflix 等)消费内容的全球转变,使用数据驱动的分析有很多机会。 例如,Netflix 会查看用户观看的电影并预测他们会喜欢什么。 此预测用于确定出现的建议电影。 但是,在本课程中,我们不会像这样面对“面向业务”的数据,而是会看到客户端如何利用互联网作为数据库。 如此庞大的数据量从未如此轻松地获得过。 我们将使用网页抓取技术来收集数据,然后在 Jupyter 中使用交互式可视化工具对其进行探索。
交互式可视化是数据表示的一种可视形式,可以帮助用户使用图形或图表理解数据。 交互式可视化帮助开发人员或分析人员以简单的形式显示数据,非技术人员也可以理解。
课程目标
在本课程中,您将:
- 分析 HTTP 请求的工作方式
- 从网页抓取表格数据
- 建立和转换 Pandas
DataFrame - 创建交互式可视化
抓取网页数据
本着利用互联网作为数据库的精神,我们可以考虑通过抓取内容或与 Web API 接口从网页获取数据。 通常,抓取内容意味着使计算机读取旨在以人类可读格式显示的数据。 这与 Web API 有所不同,Web API 以机器可读格式(最常见的是 JSON)传递数据。
在本主题中,我们将集中在网页抓取上。 确切的执行过程将取决于页面和所需的内容。 但是,正如我们将看到的,只要我们对基本概念和工具有所了解,就很容易从 HTML 页面中抓取所需的任何内容。 在本主题中,我们将以维基百科为例,并从文章中获取表格内容。 然后,我们将应用相同的技术从完全独立的域中的页面中抓取数据。 但是首先,我们将花一些时间介绍 HTTP 请求。
子主题 A:HTTP 请求简介
超文本传输协议(简称 HTTP)是互联网数据通信的基础。 它定义了如何请求页面以及响应的外观。 例如,客户可以请求亚马逊的笔记本电脑页面出售,谷歌搜索本地餐馆或他们的 Facebook 订阅。 该请求连同 URL 一起将包含用户代理和 请求标头内容中的可用浏览 cookie。 用户代理告诉服务器客户端正在使用哪种浏览器和设备,通常用于提供最用户友好的网页响应版本。 也许他们最近已经登录到网页了; 此类信息将存储在 Cookie 中,该 Cookie 可用于自动登录用户。
借助 Web 浏览器,HTTP 请求和响应的这些详细信息已在后台处理。 对我们来说幸运的是,今天当使用高级语言(例如 Python)发出请求时,情况也是如此。 出于许多目的,可以很大程度上忽略请求标头的内容。 除非另有说明,否则在请求 URL 时会在 Python 中自动生成它们。 尽管如此,出于故障排除和理解我们的请求所产生的响应的目的,对 HTTP 有基本的了解还是很有用的。
HTTP 方法有很多类型,例如 GET,HEAD,POST 和 PUT。 前两个用于请求将数据从服务器发送到客户端,而后两个用于将数据发送到服务器。
下表中总结了这些 HTTP 方法:
| HTTP 方法 | 描述 |
|---|---|
| GET | 从指定的 URL 检索信息 |
| HEAD | 从指定 URL 的 HTTP 标头中检索元信息 |
| POST | 发送附加信息以附加到指定 URL 的资源 |
| PUT | 发送附加信息以替换指定 URL 上的资源 |
每次我们在浏览器中输入网页地址并按输入时,都会发送 GET 请求。 对于 Web 抓取,通常这是我们感兴趣的唯一 HTTP 方法,也是在本课程中将使用的唯一方法。
发送请求后,可以从服务器返回各种响应类型。 这些被标记为,具有 100 级到 500 级代码,其中代码的第一位数字表示响应类别。 这些可以描述如下:
- 1xx:信息响应,例如,服务器正在处理请求。 看到这种情况并不常见。
- 2xx:例如,页面已正确加载成功。
- 3xx:重定向,例如,请求的资源已移动,并且我们已重定向到新的 URL。
- 4xx:客户端错误,例如,请求的资源不存在。
- 5xx:服务器错误,例如,网站服务器接收的流量过多,无法满足请求。
为了进行网页抓取,我们通常只关心响应类,即响应代码的第一位。 但是,每个类中都存在响应的子类别,这些子类别提供了所发生事件的更多粒度。 例如,401 代码表示未经授权响应,而 404 代码表示未找到页面响应。 这种区别是值得注意的,因为 404 表示我们请求的页面不存在,而 401 则表示我们需要登录才能查看特定资源。
让我们看看如何在 Python 中完成 HTTP 请求,并使用 Jupyter 笔记本探索其中一些主题。
子主题 B:在 Jupyter 笔记本中发出 HTTP 请求
既然我们已经讨论了 HTTP 请求如何工作以及应该期望哪种类型的响应,让我们看看如何在 Python 中完成此工作。 我们将使用一个名为 Requests 的库,该库恰好是 Python 上下载次数最多的外部库。 可以使用 Python 的内置工具,例如urllib来发出 HTTP 请求,但是请求要直观得多,事实上,在官方 Python 文档中,建议使用 urllib。
请求是用于进行简单和高级 Web 请求的绝佳选择。 它允许对标头,cookie 和授权进行各种自定义。 它跟踪重定向并提供用于返回特定页面内容(例如 JSON)的方法。 此外,还有一整套高级功能。 但是,它不允许呈现 JavaScript。
注意
通常,服务器会返回包含 JavaScript 代码段的 HTML,这些 HTML 代码段会在加载时自动在浏览器中运行。 使用“请求”使用 Python 请求内容时,此 JavaScript 代码是可见的,但不会运行。 因此,将丢失通过此操作将要更改或创建的任何元素。 通常,这并不影响获取所需信息的能力,但是在某些情况下,我们可能需要呈现 JavaScript 才能正确刮取页面。 为此,我们可以使用 Selenium 之类的库。 它具有与 Requests 库类似的 API,但提供了使用 Web 驱动程序呈现 JavaScript 的支持。
让我们深入研究以下部分,在 Jupyter 笔记本中使用带有 Python 的 Requests 库。
在 Jupyter 笔记本中使用 Python 处理 HTTP 请求
-
通过执行
jupyter notebook从项目目录启动NotebookApp。 导航到lesson-3目录并打开lesson-3-workbook.ipynb文件。 找到顶部附近的单元格,然后将其装入。我们将请求一个网页,然后检查响应对象。 有许多不同的库可用于发出请求,也有许多选择方法来精确地处理每个请求。 我们仅使用 Requests 库,因为它提供了出色的文档,高级功能和简单的 API。
-
向下滚动至
Subtopic A: Introduction to HTTP requests,然后运行该部分中的第一个单元格以导入请求库。 然后,通过运行包含以下代码的单元格来准备请求:url = 'https://jupyter.org/' req = requests.Request('GET', url) req.headers['User-Agent'] = 'Mozilla/5.0' req = req.prepare()我们使用
Request类来准备对jupyter.org主页的 GET 请求。 通过将用户代理指定为Mozilla/5.0,我们要求提供适合标准台式机浏览器的响应。 最后,我们准备请求。 -
通过运行包含
req?的单元格,为“准备的请求”req打印文档字符串:![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook]()
查看其用法,我们看到如何使用会话发送请求。 这类似于打开 Web 浏览器(开始会话)然后请求 URL。
-
通过运行以下代码,发出请求并将响应存储在名为
page的变量中:with requests.Session() as sess: page = sess.send(req)此代码返回 HTTP 响应,该变量由
page引用。 通过使用with语句,我们初始化了一个范围限于缩进代码块的会话。 这意味着我们不必担心显式关闭会话,因为它是自动完成的。 -
运行笔记本中的下两个单元格以调查响应。
page的字符串表示应指示 200 状态码响应。 这应该与status_code属性一致。 -
将响应文本保存到
page_html变量中,并使用page_html[:1000]看一下字符串的开头:![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook]()
如预期的那样,响应为 HTML。 我们可以在 BeautifulSoup 的帮助下更好地格式化此输出,该库将在本节后面的内容中广泛用于 HTML 解析。
-
通过运行以下命令来打印格式化的 HTML 的标题:
from bs4 import BeautifulSoup print(BeautifulSoup(page_html, 'html.parser').prettify()[:1000])我们导入 BeautifulSoup,然后打印漂亮的输出,其中的换行根据 HTML 结构中的层次结构缩进。
-
我们可以更进一步,使用 IPython 显示模块在 Jupyter 中实际显示 HTML。 通过运行以下代码来执行此操作:
from IPython.display import HTML HTML(page_html)![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook]()
鉴于没有运行 JavaScript 代码且未加载任何外部资源,在这里,我们将尽可能地看到呈现的 HTML。 例如,不会呈现在
jupyter.org服务器上的图像,而是显示alt文本:编程图标圆圈,jupyter 徽标等,依此类推。 -
让我们将其与实时网站进行比较,可以使用
IFrame在 Jupyter 中打开它。 通过运行以下代码来执行此操作:from IPython.display import IFrame IFrame(src=url, height=800, width=800)![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook]()
在这里,我们看到了完整的网站,包括 JavaScript 和外部资源。 实际上,我们甚至可以单击超链接并将这些页面加载到
IFrame中,就像常规浏览会话一样。 -
在使用
IFrame后,最好关闭它。 这样可以防止它耗尽内存和处理能力。 可以通过从 Jupyter 笔记本的“单元格”菜单中选择单元格并单击“当前输出 | 清除”来关闭它。
回想一下我们如何使用准备好的请求和会话在 Python 中以字符串形式请求此内容。 通常,这是使用速记方法来完成的。 缺点是我们没有太多的请求标头自定义,但这通常很好。
- 通过运行以下代码来向
http://www.python.org/发出请求:
```py
url = 'http://www.python.org/'page = requests.get(url)
page
<Response [200]>
```
页面的字符串表示形式(如单元格下方所示)应指示 200 状态代码,指示成功响应。
- 运行接下来的两个单元格。 在这里,我们打印页面的
url和history属性。
返回的 URL 不是我们输入的; 注意区别吗? 我们从[输入 URL](http://www.python.org/) 重定向到[该页面的安全版本](https://www.python.org/)。 在协议的 URL 开头,附加的`s`表示差异。 任何重定向都存储在`history`属性中; 在这种情况下,我们在此处找到一个页面,其状态码为 301(永久重定向),与所请求的原始 URL 相对应。
现在,我们可以轻松地发出请求了,我们将注意力转向解析 HTML。 这可能是一门艺术,因为通常有多种处理方法,而最好的方法通常取决于所讨论的特定 HTML 的细节。
子主题 C:在 Jupyter 笔记本中解析 HTML
从网页上抓取数据时,发出请求后,我们必须从响应内容中提取数据。 如果内容是 HTML,则最简单的方法是使用高级解析库(例如 BeautifulSoup)。 这并不是说它是的唯一方法; 原则上,可以使用正则表达式或 Python 字符串方法(例如split)来选择数据,但是采用这些选项中的任何一个都会浪费时间,并且很容易导致错误。 因此,通常对此并不满意,建议使用可信赖的解析工具。
为了了解如何从 HTML 中提取内容,了解 HTML 的基础很重要。 首先,HTML 代表超文本标记语言。 类似于 Markdown 或 XML(可扩展标记语言),它只是一种用于标记文本的语言。 在 HTML 中,显示文本包含在 HTML 元素的内容部分中,其中元素属性指定该元素在页面上的显示方式。

如上图所示,查看 HTML 元素的剖析,我们看到起始标签和结束标签之间包含的内容。 在此示例中,段落的标签为<p>; 其他常见的标签类型是<div>(文本块),<table>(数据表),<h1>(标题),<img>(图像)和<a>(超链接)。 标签具有属性,可以保存重要的元数据。 最常见的是,此元数据用于指定元素文本应如何在页面上显示。 这是 CSS 文件起作用的地方。 这些属性可以存储其他有用的信息,例如<a>标签中的href超链接(指定 URL 链接)或<img>标签中的alt替代标签,该标签指定在无法加载图像资源时显示的内容。
现在,让我们将注意力转移回 Jupyter 笔记本并解析一些 HTML! 尽管在执行本节时没有必要,但在实际情况下,使用 Chrome 或 Firefox 中的开发人员工具来帮助识别感兴趣的 HTML 元素非常有帮助。 我们将在下一节中介绍如何使用 Chrome 进行操作。
在 Jupyter 笔记本中使用 Python 解析 HTML
-
在
lesson-3-workbook.ipynb文件中,滚动到Subtopic B: Parsing HTML with Python的顶部。在本节中,我们将抓取每个国家的中央银行利率,如维基百科报道的。 在深入研究代码之前,让我们首先打开包含此数据的网页。
-
在网络浏览器中打开
https://en.wikipedia.org/wiki/List_of_countries_by_central_bank_interest_ratesURL。 如有可能,请使用 Chrome。在本节的后面,我们将向您展示如何使用 Chrome 的开发人员工具查看和搜索 HTML。在页面上,我们看到的内容很少,只有一大堆国家/地区和它们的利率。 这是我们要抓取的表。
-
返回 Jupyter 笔记本并将 HTML 作为 BeautifulSoup 对象加载,以便可以对其进行解析。 通过运行以下代码来执行此操作:
from bs4 import BeautifulSoup soup = BeautifulSoup(page.content, 'html.parser')我们使用 Python 的默认
html.parser作为解析器,但是如果需要,可以使用lxml之类的第三方解析器。通常,当使用类似 BeautifulSoup 这样的新对象时,最好通过执行
soup?拉取文档字符串。 但是,在这种情况下,文档字符串不是特别有用。 探索 Python 对象的另一个工具是pdir,它列出了对象的所有属性和方法(可以与pip install pdir2一起安装)。 它基本上是 Python 内置dir函数的格式化版本。 -
通过运行以下代码,显示 BeautifulSoup 对象的属性和方法。 无论是否安装了
pdir外部库,它都将运行:try:import pdirdir = pdir except: print('You can install pdir with:\npip install pdir2') dir(soup)在这里,我们看到可以在
soup上调用的的方法和属性的列表。 最常用的函数可能是find_all,它返回与给定条件匹配的元素列表。 -
使用以下代码获取页面的
h1标题:h1 = soup.find_all('h1') h1 >> [<h1 class="firstHeading" id="firstHeading" lang="en">List of countries by central bank interest rates</h1>]通常,页面只有一个 H1 元素,因此很明显,我们在这里只找到一个。
-
运行下几个单元格。 我们使用
h1 = h1[0]将h1重新定义为第一个(也是唯一的)列表元素,然后使用h1.attrs打印出 HTML 元素属性:>> {'class': ['firstHeading'], 'id': 'firstHeading', 'lang': 'en'}我们看到了该元素的类和 ID,CSS 代码都可以引用它们来定义该元素的样式。
-
通过打印
h1.text获得 HTML 元素内容(即可见文本)。 -
通过运行以下代码来获取页面上的所有图像:
imgs = soup.find_all('img') len(imgs) >> 91页面上有很多图像。 其中大多数是针对国旗的。
-
通过运行以下代码来打印每个图像的源:
[element.attrs['src'] for element in imgsif 'src' in element.attrs.keys()]![Parsing HTML with Python in a Jupyter Notebook]()
我们使用列表理解来遍历元素,选择每个元素的
src属性(只要该属性实际可用)。现在,让我们爬取表格。 我们将使用 Chrome 的开发人员工具来查找其中包含的元素。
-
如果尚未完成,请在 Chrome 中打开我们正在查看的维基百科页面。 然后,在浏览器中,从视图菜单中选择开发人员工具。 侧栏将打开。 可从开发人员工具的元素标签中查看 HTML。
-
选择工具侧栏左上方的小箭头。 这使我们可以将鼠标悬停在页面上,并在侧栏的“元素”部分中查看 HTML 元素的位置:

- 将鼠标悬停在正文上以查看该表如何包含在具有
id = "bodyContent"的div中:

- 通过运行以下代码选择该
div:
```py
body_content = soup.find('div', {'id': 'bodyContent'})
```
现在,我们可以在完整 HTML 的此子集中查找该表。 通常,表被组织为标题`<th>`,行`<tr>`和数据条目`<td>`。
- 通过运行以下代码来获取表头:
```py
table_headers = body_content.find_all('th')[:3]table_headers
>>> [<th>Country or<br/>
currency union</th>, <th>Central bank<br/>
interest rate (%)</th>, <th>Date of last<br/>
change</th>]
```
在这里,我们看到三个标题。 每个内容中都有一个换行元素`<br/>`,这将使文本更难于清晰地解析。
- 通过运行以下代码获取文本:
```py
table_headers = [element.get_text().replace('\n', ' ')for element in table_headers]table_headers
>> ['Country or currency union',
'Central bank interest rate (%)',
'Date of last change']
```
在这里,我们使用`get_text`方法获取内容,然后运行`string.replace`方法以删除`<br/>`元素产生的换行符。
为了获取数据,我们将首先执行一些测试,然后将所有数据刮到单个单元格中。
- 通过运行以下代码,获取第二个
<tr>(行)元素中每个单元格的数据:
```py
row_number = 2d1, d2, d3 = body_content.find_all('tr')[row_number]\.find_all('td')
```
我们找到所有行元素,挑选出第三个行元素,然后在其中找到三个数据元素。
让我们看一下结果数据,看看如何解析每一行中的文本。
- 运行接下来的两个单元格以打印
d1及其text属性:

我们在前面遇到了一些不良字符。 可以通过仅搜索`<a>`标签的文本来解决此。
- 运行
d1.find('a').text以返回该单元格正确的清晰数据。 - 在接下来的两个单元格中运行,以打印
d2及其文本。 此数据看起来足够干净,可以直接转换为浮点数。 - 运行接下来的两个单元格以打印
d3及其文本:

与`d1`相似,我们看到最好只获取`span`元素的文本。
- 通过运行以下代码来正确解析此表条目的日期:
```py
d3.find_all('span')[1].text
>> '30 June 2016'
```
- 现在,我们准备通过遍历行元素
<th>来执行完整的抓取。 运行以下代码:
```py
data = []for i, row in enumerate(body_content.find_all('tr')):...
...
>> Ignoring row 101 because len(data) != 3
>> Ignoring row 102 because len(data) != 3
```
### 注意
有关完整代码,请参考`Lesson 3`文件夹中的`Lesson 3.txt`文件。
我们遍历所有行,而忽略任何包含比三个数据元素更多的的行。 这些行将不与我们感兴趣的表中的数据相对应。表中确实包含三个数据元素的行被假定为在表中,并且我们在测试过程中从中解析出了这些文本。
文本解析是在`try`/`except`语句内完成的,该语句将捕获任何错误,并允许在不停止迭代的情况下跳过此行。 由于该语句而引起错误的任何行都应查看。 这些数据可以手动记录,也可以通过更改抓取循环并重新运行来解决。 在这种情况下,为了节省时间,我们将忽略所有错误。
- 通过运行
print(data[:10]):
```py
>> [['Albania', 1.25, '4 May 2016'],
['Angola', 16.0, '30 June 2016'],
['Argentina', 26.25, '11 April 2017'],
['Armenia', 6.0, '14 February 2017'],
['Australia', 1.5, '2 August 2016'],
['Azerbaijan', 15.0, '9 September 2016'],
['Bahamas', 4.0, '22 December 2016'],
['Bahrain', 1.5, '14 June 2017'],
['Bangladesh', 6.75, '14 January 2016'],
['Belarus', 12.0, '28 June 2017']]
```
打印抓取的数据列表的开头
- 在本课程的稍后部分,我们将可视化此数据。 现在,通过运行以下代码将数据保存到 CSV 文件:
```py
f_path = '../data/countries/interest-rates.csv'with open(f_path, 'w') as f:f.write('{};{};{}\n'.format(*table_headers))for d in data:f.write('{};{};{}\n'.format(*d))
```
请注意,我们使用分号来分隔字段。
活动 A:使用 Jupyter 笔记本的网页爬取
我们将获得每个国家的人口。 然后,在下一个主题中,此将与一起可视化为上一节中提取的利率数据。
既然我们已经了解了 Web 抓取的基础知识,那么让我们将相同的技术应用于新的网页并抓取更多数据!
注意
自创建此文档以来,此页面可能已更改。 如果此 URL 不再指向国家/地区人口表,请改用此维基百科页面。
-
对于此页面,可以使用以下代码段抓取数据:
data = [] for i, row in enumerate(soup.find_all('tr')): row_data = row.find_all('td') try: d1, d2, d3 = row_data[1], row_data[5], row_data[6] d1 = d1.find('a').text d2 = float(d2.text) d3 = d3.find_all('span')[1].text.replace('+', '') data.append([d1, d2, d3]) except: print('Ignoring row {}'.format(i)) -
在
lesson-3-workbook.ipynbJupyter 笔记本中,滚动到Activity A: Web scraping with Python。 -
设置
url变量,并通过运行以下代码在笔记本中加载页面的IFrame:url = 'http://www.worldometers.info/world-population/population-by-country/' IFrame(url, height=300, width=800)页面应在笔记本电脑中加载。 向下滚动,我们可以看到按人口标题显示的世界国家及其人口表。 我们将从此表中刮取前三列,以获取国家/地区,人口和年度人口变化。
-
通过选择单元格并从 Jupyter 笔记本的单元格菜单中单击当前输出 | 清空,关闭
IFrame。 -
通过运行以下代码来请求页面并将其作为
BeautifulSoup对象加载:page = requests.get(url)soup = BeautifulSoup(page.content, 'html.parser')我们将页面内容提供给
BeautifulSoup构造器。 回想一下,之前我们在这里使用了page.text。 不同之处在于page.content返回原始二进制响应内容,而page.text返回 UTF-8 解码的内容。 通常最好的做法是传递bytes对象并让BeautifulSoup对其进行解码,而不是使用page.text进行请求。 -
通过运行以下代码,为页面打印
h1:soup.find_all('h1') >> [<h1>Countries in the world by population (2017)</h1>]与上一节一样,我们将通过搜索
<th>,<tr>和<td>元素来刮除表格。 -
通过运行以下代码来获取并打印表标题:
table_headers = soup.find_all('th')table_headers >> [<th>#</th>, <th>Country (or dependency)</th>, <th>Population<br/> (2017)</th>, <th>Yearly<br/> Change</th>, <th>Net<br/> Change</th>, <th>Density<br/> (P/Km²)</th>, <th>Land Area<br/> (Km²)</th>, <th>Migrants<br/> (net)</th>, <th>Fert.<br/> Rate</th>, <th>Med.<br/> Age</th>, <th>Urban<br/> Pop %</th>, <th>World<br/> Share</th>] -
我们只对前三列感兴趣。 选择这些并使用以下代码解析文本:
table_headers = table_headers[1:4] table_headers = [t.text.replace('\n', '') for t in table_headers]选择所需的表头子集后,我们将解析每个表头的文本内容并删除所有换行符。
现在,我们将获取数据。 遵循与上一节相同的规定,我们将测试如何解析示例行的数据。
-
通过运行以下代码来获取示例行的数据:
row_number = 2row_data = soup.find_all('tr')[row_number]\ .find_all('td') -
我们有几列数据? 通过运行
print(len(row_data))打印row_data的长度。 -
通过运行
print(row_data[:4])打印第一个元素:
```py
>> [<td>2</td>,
<td style="font-weight: bold; font-size:15px; text-align:left"><a href="/world-population/india-population/">India</a></td>,
<td style="font-weight: bold;">1,339,180,127</td>,
<td>1.13 %</td>]
```
很明显,我们要选择列表索引 1、2 和 3。第一个数据值可以忽略,因为它只是索引。
- 通过运行以下代码,选择我们感兴趣的数据元素:
```py
d1, d2, d3 = row_data[1:4]
```
- 查看
row_data输出,我们可以找到如何正确解析数据的方法。 我们想在第一个数据元素中选择<a>元素的内容,然后简单地从其他元素中获取文本。 通过运行以下代码来测试这些假设:
```py
print(d1.find('a').text)print(d2.text)print(d3.text)
>> India
>> 1,339,180,127
>> 1.13 %
```
出色的! 这看起来运作良好。 现在,我们准备爬取整个表格。
- 通过运行以下代码来抓取并解析表数据:
```py
data = []for i, row in enumerate(soup.find_all('tr')):try:d1, d2, d3 = row.find_all('td')[1:4]d1 = d1.find('a').textd2 = d2.textd3 = d3.textdata.append([d1, d2, d3])except:print('Error parsing row {}'.format(i))
>> Error parsing row 0
```
这与之前非常相似,我们尝试解析文本,并在出现错误时跳过该行。
- 通过运行
print(data[:10])打印刮取数据的头部:
```py
>> [['China', '1,409,517,397', '0.43 %'],
['India', '1,339,180,127', '1.13 %'],
['U.S.', '324,459,463', '0.71 %'],
['Indonesia', '263,991,379', '1.10 %'],
['Brazil', '209,288,278', '0.79 %'],
['Pakistan', '197,015,955', '1.97 %'],
['Nigeria', '190,886,311', '2.63 %'],
['Bangladesh', '164,669,751', '1.05 %'],
['Russia', '143,989,754', '0.02 %'],
['Mexico', '129,163,276', '1.27 %']]
```
看来我们已经成功抓取了数据! 请注意,尽管此网页完全不同,但与维基百科相比,此表的处理过程有多相似。 当然,并不总是将数据包含在表中,但是不管怎样,我们通常可以使用`find_all`作为主要的解析方法。
- 最后,将数据保存到 CSV 文件中,以备后用。 通过运行以下代码来执行此操作:
```py
f_path = '../data/countries/populations.csv'with open(f_path, 'w') as f:f.write('{};{};{}\n'.format(*table_headers))for d in data:
f.write('{};{};{}\n'.format(*d))
```
总而言之,我们已经了解了 Jupyter 笔记本如何用于网络抓取。 我们通过学习 HTTP 方法和状态代码开始了本课程。 然后,我们使用 Requests 库使用 Python 实际执行 HTTP 请求,并了解了 BeautifulSoup 库如何用于解析 HTML 响应。
事实证明,我们的 Jupyter 笔记本是进行此类工作的好工具。 我们能够浏览 Web 请求的结果,并尝试各种 HTML 解析技术。 我们还能够呈现 HTML 甚至在笔记本中加载网页的实时版本!
在本课程的下一个主题中,我们转到一个全新的主题:交互式可视化。 我们将看到如何在笔记本中直接创建和显示交互式图表,以及如何使用这些图表来浏览我们刚刚收集的数据。
交互式可视化
可视化作为从数据集中提取信息的一种非常有用的方法。 例如,与查看表中的值相比,使用条形图很容易区分值分布。 当然,正如我们在本书前面所看到的,可以将它们用于研究数据集中的模式,否则这些模式将很难识别。 此外,它们可用于帮助向陌生方解释数据集。 例如,如果它们包含在博客文章中,它们可以提高读者的兴趣水平,并可以用来拆分文本块。
考虑交互式可视化时,其好处类似于静态可视化,但由于可以允许查看者方面的主动探索而有所增强。 它们不仅使观看者可以回答有关数据的问题,而且在浏览时还会想到新问题。 这可以使诸如博客阅读器或同事之类的单独团体受益,但也可以使创建者受益,因为它允许在不更改任何代码的情况下轻松地对数据进行临时的特别探索。
在本主题中,我们将讨论并演示如何使用 Bokeh 在 Jupyter 中构建交互式可视化。 但是,在此之前,我们将简要回顾 Pandas DataFrame,它们在使用 Python 进行数据可视化中起着重要作用。
子主题 A:构建用于存储和组织数据的数据帧
正如在本书中一次又一次地看到的那样,Pandas 是使用 Python 和 Jupyter 笔记本进行数据科学不可或缺的一部分。DataFrame提供了一种组织和存储带标签数据的方法,但是更重要的是,Pandas 提供了节省时间的方法来转换DataFrame中的数据。 我们在本书中看到的示例包括删除重复项,将字典映射到列,在列上应用函数以及填写缺失值。
关于可视化,DataFrame提供了用于创建各种 Matplotlib 图的方法,包括df.plot.barh(),df.plot.hist()等。 交互式可视化库 Bokeh 以前依靠 Pandas DataFrame来获取高级图表。 它们的工作方式与 Seaborn 相似,就像我们在上一课前面所看到的那样,其中DataFrame与要绘制的特定列一起传递给了绘图函数。 但是,Bokeh 的最新版本已放弃对此行为的支持。 相反,现在可以使用与 Matplotlib 相同的方式创建图,在图中可以将数据存储在简单列表或 NumPy 数组中。 讨论的重点是,DataFrame并非完全必要,但对于在可视化之前组织和操作数据仍然非常有帮助。
建立和合并 Pandas DataFrame
让我们直接进行练习,在该练习中,我们将继续处理之前抓取的国家/地区数据。 回想一下,我们提取了每个国家的中央银行利率和人口,并将结果保存在 CSV 文件中。 我们将从这些文件中加载数据,然后将它们合并到一个DataFrame中,然后将其用作后续交互式可视化的数据源。
-
在
lesson-3-workbook.ipynbJupyter 笔记本中,滚动到Topic B部分中的Subtopic A: Building a DataFrame to store and organize data子部分。我们首先要从 CSV 文件中加载数据,以使其恢复为抓取后的状态。 与使用
pd.read_csv函数相反,这将允许我们练习从 Python 对象构建DataFrame。注意
使用
pd.read_csv时,将从字符串输入中推断出每一列的数据类型。 另一方面,当我们在此处使用pd.DataFrame时,则将数据类型作为输入变量的类型。正如我们将看到的,在我们的例子中,我们读取文件并且不费心地将变量转换为数字或日期时间,直到实例化
DataFrame之后。 -
通过运行以下代码,将 CSV 文件加载到列表中:
with open('../data/countries/interest-rates.csv', 'r') as f: int_rates_col_names = next(f).split(',') int_rates = [line.split(',') for line in f.read().splitlines()] with open('../data/countries/populations.csv', 'r') as f: populations_col_names = next(f).split(',') populations = [line.split(',') for line in f.read().splitlines()] -
通过运行接下来的两个单元,检查结果列表的外观。 我们应该看到类似于以下的输出:
print(int_rates_col_names)int_rates[:5] >> ['Country or currency union', 'Central bank interest ... ... ['Indonesia', '263', '991', '379', '1.10 %'], ['Brazil', '209', '288', '278', '0.79 %']]注意
有关完整的代码,请参考
Lesson 3文件夹中的Lesson 3.txt文件。现在,数据采用标准的 Python 列表结构,就像从上一部分中的网页抓取后一样。 现在,我们将创建两个
DataFrame并将其合并,以便将所有数据组织在一个对象中。 -
使用标准的
DataFrame构造器通过运行以下代码来创建两个DataFrame:df_int_rates = pd.DataFrame(int_rates, columns=int_rates_col_names) df_populations = pd.DataFrame(populations, columns=populations_col_names)这不是我们第一次在本书中使用。 在这里,我们传递数据列表(如前所述)和相应的列名。 输入数据也可以是字典类型的,当每列包含在单独的列表中时,这将很有用。
接下来,我们将清理每个
DataFrame。 从利率一开始,让我们打印头和尾,并列出数据类型。 -
显示整个
DataFrame时,默认最大行数为 60(对于版本 0.18.1)。 通过运行以下代码,将其减少到 10 个:pd.options.display.max_rows = 10 -
通过运行以下代码来显示利率数据帧的开头和结尾:
df_int_rates![Building and merging Pandas DataFrames]()
-
通过运行以下命令打印数据类型:
df_int_rates.dtypes >> Country or currency union object >> Central bank interest rate (%) object >> Date of last change object >> dtype: objectPandas 已将每一列分配为字符串数据类型,这很有意义,因为输入变量都是字符串。 我们将分别将它们更改为字符串,浮点型和日期时间。
-
通过运行以下代码,转换为正确的数据类型:
df_int_rates['Central bank interest rate (%)'] = \df_int_rates['Central bank interest rate (%)']\.astype(float, copy=False)df_int_rates['Date of last change'] = \ pd.to_datetime(df_int_rates['Date of last change'])我们使用
astype将利率值转换为浮点数,将copy=False设置为节省内存。 由于日期值以这种易于阅读的格式给出,因此可以简单地使用pd.to_datetime进行转换。 -
通过运行以下代码,检查每列的新数据类型:
df_int_rates.dtypes >> Country or currency union object >> Central bank interest rate (%) float64 >> Date of last change datetime64[ns] >> dtype: object可以看出,现在所有内容都采用了正确的格式。
-
让我们对另一个
DataFrame应用相同的过程。 运行接下来的几个单元格以对df_populations重复前面的步骤:
```py
df_populations
```

然后,运行以下代码:
```py
df_populations['Population (2017)'] = df_populations['Population (2017)']\.str.replace(',', '')\.astype(float, copy=False)df_populations['Yearly Change'] = df_populations['Yearly Change']\.str.rstrip('%')\
.astype(float, copy=False)
```
要将数字列转换为浮点数,在这种情况下,我们必须首先对字符串进行一些修改。 我们使用字符串方法从总体中去除了所有逗号,并从“年度更改”列中删除了百分号。
现在,我们将在每行的国家/地区名称上合并数据帧。 请记住,这些仍然是从网络上抓取的原始国家/地区名称,因此匹配字符串可能涉及一些工作。
- 通过运行以下代码来合并
DataFrame:
```py
df_merge = pd.merge(df_populations,
df_int_rates,
left_on='Country (or dependency)',
right_on='Country or currency union',
how='outer'
df_merge
```
我们在左侧的`DataFrame`中传递人口数据,在右侧的`DataFrame`中传递利率,在`country`列上执行外部匹配。 这将导致`NaN`值,两者不重叠。
- 为了节省时间,让我们看看人口最多的国家/地区,看看我们是否错过了匹配的国家/地区。 理想情况下,我们希望检查所有内容。 通过运行以下代码查看人口最多的国家:
```py
df_merge.sort_values('Population (2017)', ascending=False)\.head(10)
```

看起来美国没有匹配。 这是因为它在利率数据中被列为*美国*。 让我们对此进行补救。
- 通过运行以下代码,在人口表中修复美国的标签:
```py
col = 'Country (or dependency)'
df_populations.loc[df_populations[col] == 'U.S.'] = 'United States'
```
我们使用`loc`方法重命名人口数据帧所在的国家/地区,以定位该行。 现在,让我们正确合并`DataFrame`。
- 重新合并国家/地区名称上的数据帧,但这一次使用内部合并删除
NaN值:
```py
df_merge = pd.merge(df_populations,df_int_rates,left_on='Country (or dependency)',right_on='Country or currency union',
how='inner')
```
- 在合并的
DataFrame中,我们剩下两个相同的列。 通过运行以下代码来删除其中之一:
```py
del df_merge['Country or currency union']
```
- 通过运行以下代码来重命名列:
```py
name_map = {'Country (or dependency)': 'Country','Population (2017)': 'Population','Central bank interest rate (%)': 'Interest rate'}df_merge = df_merge.rename(columns=name_map)
```
我们剩下了以下合并和清除的`DataFrame`:

- 现在我们已经将所有数据存储在一个组织良好的表中,我们可以将转到有趣的部分:对其进行可视化。 让我们将该表保存到 CSV 文件中以备后用,然后继续讨论如何使用 Bokeh 创建可视化。 将合并的数据写入 CSV 文件,以供以后使用,并使用以下代码:
```py
df_merge.to_csv('../data/countries/merged.csv', index=False)
```
子主题 B:Bokeh 简介
Bokeh 是一个用于 Python 的交互式可视化库。 其目标是提供与 D3(流行的 JavaScript 交互式可视化库)相似的功能。 Bokeh 的功能与 D3 完全不同,鉴于 Python 和 JavaScript 之间的差异,这并不奇怪。 总体而言,它要简单得多,并且不允许进行与 D3 一样多的自定义。 但是,这样做很方便,因为它易于使用,并且仍然拥有我们在本节中将探讨的一系列出色功能。
让我们直接学习 Jupyter 笔记本的快速练习,并通过示例介绍 Bokeh。
注意
网上有关于 Bokeh 的好的文档,但是其中很多已经过时了。 在 Google 中搜索Bokeh bar plot之类的内容仍会趋向于查找不再存在的旧模块的文档,例如,以前可通过bokeh.charts(0.12.0 版之前)使用的高级绘图工具。 这些是将 Pandas DataFrame作为输入的方式,与 Seaborn 绘图功能的方式几乎相同。 删除高级绘图工具模块已简化了 Bokeh,并将使将来的开发更加集中。 现在,绘图工具大致分为bokeh.plotting模块,如在下一个练习和后续活动中所见。
Bokeh 交互式可视化介绍
我们将加载所需的 Bokeh 模块,并显示一些可以使用 Bokeh 进行的简单交互式绘图。 请注意,本书中的示例是使用 Bokeh 的 0.12.10 版设计的。
-
在
lesson-3-workbook.ipynbJupyter 笔记本中,滚动到Subtopic B: Introduction to Bokeh。 -
像 Scikit-learn 一样,Bokeh 模块通常是分段加载的(例如,与 Pandas 不同,一次加载整个库)。 通过运行以下代码导入一些基本的绘图模块:
from bokeh.plotting import figure, show, output_notebookoutput_notebook()我们需要运行
output_notebook()以便在 Jupyter 笔记本中渲染交互式视觉效果。 -
通过运行以下代码生成随机数据用于绘图:
np.random.seed(30)data = pd.Series(np.random.randn(200),index=list(range(200)))\.cumsum()x = data.indexy = data.values使用大约为零分布的一组随机数字的累积和来生成随机数据。 例如,这种影响是一种类似于股票价格时间序列的趋势。
-
通过运行以下代码在 Bokeh 中用线图绘制数据:
p = figure(title='Example plot', x_axis_label='x', y_axis_label='y')p.line(x, y, legend='Random trend')show(p)![Introduction to interactive visualizations with Bokeh]()
我们实例化该图(由变量
p引用),然后绘制一条线。 在 Jupyter 中运行此程序会产生,它是一个交互式图形,右侧带有各种选项。前三个选项(从 0.12.10 版本开始)是平移,盒子缩放 和滚轮缩放。 尝试这些,并尝试它们的工作方式。 使用重置选项可重新加载默认绘图限制。
-
其他图可以用图的替代方法来创建。 通过运行以下代码来绘制散点图,其中我们将前面代码中的
line替换为circle:size = np.random.rand(200) * 5p = figure(title='Example plot', x_axis_label='x', y_axis_label='y') p.circle(x, y, radius=size, alpha=0.5, legend='Random dots')show(p)![Introduction to interactive visualizations with Bokeh]()
在这里,我们使用一组随机的数字指定了每个圆圈的大小。
交互式可视化的一个非常诱人的功能是工具提示。 这是一个悬停工具,允许用户通过将其悬停在某点上来获取有关该点的信息。
-
为了添加此工具,我们将使用略有不同的方法来创建绘图。 这将要求我们导入几个新库。 运行以下代码:
p.circle(x, y, radius=size, alpha=0.5, legend='Random dots')show(p)这次,我们将创建一个数据源以传递到绘图方法。 它可以包含元数据,可以通过悬停工具将其包含在可视化中的。
-
通过运行以下代码,创建随机标签并使用悬停工具绘制交互式可视化效果图:
source = ColumnDataSource(data=dict( x=x, y=y, ... ... source=source, legend='Random dots') show(p)注意
有关完整的代码,请参考
Lesson 3文件夹中的Lesson 3.txt文件。我们将在此处停止介绍性练习,但在以下活动中我们将继续创建和探索绘图。
![Introduction to interactive visualizations with Bokeh]()
通过将键/值对的字典传递给
ColumnDataSource构造器,为图定义数据源。 该来源包括x位置,y位置和每个点的大小,以及每个点的随机字母A,B或C。 这些随机字母被指定为悬停工具的标签,该标签还将显示每个点的大小。 然后将悬停工具添加到图形中,并通过特定的绘制方法从每个元素中检索数据,在这种情况下为圆形。结果是,我们现在可以将鼠标悬停在这些点上,并查看我们为悬停工具选择的数据!
通过查看绘图右侧的工具栏,我们注意到通过显式包括悬停工具,其他已消失。 可以通过手动将它们添加到传递给
bokeh.plotting.figure的工具对象列表中来包括这些对象。 -
通过运行以下代码,将平移,缩放和重置工具添加到绘图中:
from bokeh.models import PanTool, BoxZoomTool, WheelZoomTool, ResetTool ... ... legend='Random dots') show(p)除了
tools变量外,该代码与先前显示的代码相同,该变量现在引用了我们从 Bokeh 库中导入的几个新工具。
活动 B:以交互式可视化方式探索数据
我们将在上一个练习的结尾处使用 Bokeh,在右边继续学习,除了代替使用在那里看到的随机生成的数据外,我们将使用在本课的第一部分中从网上抓取的数据。
要使用 Bokeh 创建我们抓取数据的交互式可视化。
-
在
lesson-3-workbook.ipynb文件中,滚动到Activity B: Interactive visualizations with Bokeh部分。 -
通过运行以下代码来加载以前抓取,合并和清理的网页数据:
df = pd.read_csv('../data/countries/merged.csv')df['Date of last change'] = pd.to_datetime(df['Date of last change']) -
通过显示
DataFrame来回顾数据的样子:![Activity B: Exploring Data with Interactive Visualizations]()
在上一个练习中,我们对学习 Bokeh 的工作方式感兴趣,而现在,我们对这种数据的外观感兴趣。 为了探索该数据集,我们将使用交互式可视化。
-
通过运行以下代码,绘制人口散点图作为利率的函数:
source = ColumnDataSource(data=dict( x=df['Interest rate'], y=df['Population'], desc=df['Country'], )) hover = HoverTool(tooltips=[ ('Country', '@desc'), ('Interest Rate (%)', '@x'), ('Population', '@y') ]) tools = [hover, PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool()] p = figure(tools=tools, x_axis_label='Interest Rate (%)', y_axis_label='Population') p.circle('x', 'y', size=10, alpha=0.5, source=source) show(p)![Activity B: Exploring Data with Interactive Visualizations]()
这与我们在上一个练习中介绍 Bokeh 时看到的最终示例非常相似。 我们为每个点设置了
x和y坐标以及国家/地区名称的自定义数据源。 此国家/地区名称已通过传递给悬停工具,因此将鼠标悬停在点上时可以看到。 我们将此工具与以及其他一组有用的工具一起传递给图中。 -
在数据中,我们看到了一些明显的异常高种群。 将鼠标悬停在上面可以查看它们是什么:
![Activity B: Exploring Data with Interactive Visualizations]()
我们看到他们属于印度和中国。 这些国家的利率相当平均。 让我们通过使用盒子缩放工具修改视图窗口大小来关注点的其余部分。
-
选择盒子缩放工具并更改查看窗口,以更好地查看大多数数据:
![Activity B: Exploring Data with Interactive Visualizations]()
![Activity B: Exploring Data with Interactive Visualizations]()
探索这些要点,看看各个国家的利率如何比较。 利率最高的国家是哪些?
![Activity B: Exploring Data with Interactive Visualizations]()
-
一些人口较少的国家似乎具有负利率。 选择“车轮缩放”工具,然后使用它来放大该区域。 如果需要,请使用“平移”工具将图重新居中,以便查看负利率样本。 将鼠标悬停在其中一些上,查看它们对应的国家/地区:
![Activity B: Exploring Data with Interactive Visualizations]()
![Activity B: Exploring Data with Interactive Visualizations]()
让我们重新绘图,根据上一次利率更改的日期添加颜色。 这对于搜索上次更改日期与利率或人口规模之间的关系很有用。
-
通过运行以下代码,将
'Year of last change'列添加到DataFrame中:def get_year(x):year = x.strftime('%Y')if year in ['2018', '2017', '2016']:return yearelse: return 'Other'df['Year of last change'] = df['Date of last change'].apply(get_year)我们首先定义一个函数,根据上次更改的年份对样本进行分组,然后将该函数应用于最近更改的日期列。 接下来,我们需要将这些值映射到颜色以进行可视化。
-
通过运行以下代码,创建映射以将上次更改日期分组为颜色类别:
year_to_color = { '2018': 'black', '2017': 'blue', '2016': 'orange', 'Other':'red' }一旦映射到上次更改的年份列,它将根据可用的类别(2018、2017、2016 和其他)为颜色分配值。 这里的颜色是标准字符串,但也可以用十六进制代码表示。
-
通过运行以下代码来创建彩色的可视化文件:
```py
source = ColumnDataSource(data=dict(
x=df['Interest rate'],
...
...
fill_color='colors', line_color='black',
legend='label')
show(p)
```
### 注意
有关完整的代码,请参考`Lesson 3`文件夹中的`Lesson 3.txt`文件。

这里有一些重要的技术细节。 首先,我们将每个点的颜色和标签添加到`ColumnDataSource`。 然后在通过设置`fill_color`和`legend`自变量绘制圆图时引用这些参数。
- 寻找模式,放大人口较少的国家:

我们可以看到黑点在图的右侧更加普遍。 这表明利率较高的国家近期更新的可能性更大。
我们尚未查看的一个数据列是人口的逐年变化。 让我们将其与利率进行可视化比较,看看是否存在任何趋势。 我们还将通过根据国家/地区人口设置圆圈大小来增强绘图效果。
- 通过运行以下代码,将利率绘制为人口年度变化的函数:
```py
source = ColumnDataSource(data=dict(x=df['Yearly Change'],...
...p.circle('x', 'y', size=10, alpha=0.5, source=source,radius='radii')show(p)
```

在这里,我们使用总体的平方根作为半径,确保也将结果按比例缩小到合适的大小以进行可视化。
我们看到人口的逐年变化与利率之间存在很强的相关性。 当我们考虑人口数量时,主要通过观察较大的圆圈,这种相关性尤其强。 让我们在图中添加一条最合适的线来说明这种相关性。
我们将使用 Scikit-learn 以国家人口(如上图所示)为权重来创建最合适的线。
- 通过运行以下代码,确定最适合先前绘制关系的线:
```py
from sklearn.linear_model import LinearRegression
X = df['Yearly Change'].values.reshape(-1, 1)
y = df['Interest rate'].values
weights = np.sqrt(df['Population'])/1e5
lm = LinearRegression()
lm.fit(X, y, sample_weight=weights)
lm_x = np.linspace(X.flatten().min(), X.flatten().max(), 50)
lm_y = lm.predict(lm_x.reshape(-1, 1))
```
本书前面的部分应该熟悉 Scikit-learn 代码。 如所承诺的那样,我们使用上图所示的转换后的总体作为权重。 然后,通过预测一系列`x`值的线性模型值来计算最佳拟合线。
为了绘制线条,我们可以重用前面的代码,向 Bokeh 中的`line`模块添加一个额外的调用。 我们还必须为此行设置一个新的数据源。
- 通过运行以下代码,重新绘制上图,添加一条最合适的线:
```py
source = ColumnDataSource(data=dict(
x=df['Yearly Change'],
y=df['Interest rate'],
...
...
p.line('x', 'y', line_width=2, line_color='red',
source=lm_source)
show(p)
```

对于线源`lm_source`,我们将`N/A`作为国家名称和人口,因为这些不是最适合的线的适用值。 通过将鼠标悬停在该行上可以看到,它们确实出现在工具提示中。
这种可视化的交互性质为我们提供了一个独特的机会来探索此数据集中的异常值,例如,右下角的小点。
- 使用缩放工具并将鼠标悬停在有趣的样本上,以探索图。 请注意以下几点:
* 乌克兰的年利率变化不大,因此利率异常高:

* 鉴于人口的逐年增加,巴林的利率异常低下:

总结
在本课程中,我们抓取了网页表,然后使用交互式可视化工具来研究数据。
我们首先查看 HTTP 请求的工作方式,重点关注 GET 请求及其响应状态代码。 然后,我们进入 Jupyter 笔记本,并使用 Requests 库通过 Python 发出 HTTP 请求。 我们了解了如何使用 Jupyter 以及可与之交互的实际网页在笔记本中呈现 HTML。 发出请求后,我们看到了如何使用 BeautifulSoup 来解析 HTML 中的文本,并使用该库来爬取表格数据。
抓取两个数据表后,我们将它们存储在 Pandas DataFrame中。 第一个表包含每个国家的中央银行利率,第二个表包含人口。 我们将它们组合到一个表中,然后用于创建交互式可视化。
最后,我们使用 Bokeh 在 Jupyter 中渲染交互式可视化效果。 我们看到了如何使用 Bokeh API 创建各种自定义图,并创建了具有特定交互功能(例如缩放,平移和悬停)的散点图。 在定制方面,我们明确显示了如何为每个数据样本设置点半径和颜色。 此外,当使用 Bokeh 探索被抓取的人口数据时,将工具提示悬停在各个点上时会显示国家名称和相关数据。
祝贺您使用 Jupyter 笔记本完成了本数据科学入门课程! 无论您在本书中使用 Jupyter 和 Python 的经验如何,您都已经学到了一些实用的数据科学实用知识!
在结束之前,让我们快速回顾一下本书中涉及的主题。
第一个课程是 Jupyter 笔记本平台的简介,其中涵盖了所有基础知识。 我们了解了界面以及如何使用和安装魔术函数。 然后,我们介绍了将要使用的 Python 库,并对波士顿房屋数据集进行了探索性分析。
在第二课中,我们专注于与 Jupyter 一起进行机器学习。 我们首先讨论了制定预测分析计划的步骤,然后介绍了几种不同类型的模型,包括 SVM,KNN 分类器和随机森林。 在处理员工保留数据集时,我们应用了数据清理方法,然后训练了模型以预测员工是否离开。 我们还探讨了更高级的主题,例如过拟合,K 折交叉验证和验证曲线。
最后,在第三课中,我们简要地从数据分析转移到使用 Web 抓取的数据收集,并了解了如何在 Jupyter 中进行 HTTP 请求和解析 HTML 响应。 然后,我们通过使用交互式可视化来探索我们收集的数据来完成本书。
我们希望您喜欢通过 Jupyter 笔记本完成所有这些工作,并希望将来可以在项目中继续使用它们!























































































浙公网安备 33010602011771号