原文:
annas-archive.org/md5/0eedd92bcc1b77abb317f4ab75395728
译者:飞龙
前言
关于
本节简要介绍了作者、书籍内容、你开始时所需的技术技能,以及完成书中所有活动和练习所需的硬件和软件要求。
关于本书
《应用数据科学:Python 和 Jupyter》将教授你入门级数据科学所需的技能。你将了解一些最常用的库,这些库是 Anaconda 发行版的一部分,然后通过真实数据集探索机器学习模型,帮助你获得在现实世界中应用这些技能的能力。最后,你将学习如何轻松地从开放网络抓取并收集数据,将这些新技能应用到可操作的实际场景中。
关于作者
Alex Galea 自从毕业于加拿大圭尔夫大学,获得物理学硕士学位后,便一直从事数据分析工作。在研究量子气体作为研究生项目的一部分时,他对 Python 产生了浓厚的兴趣。最近,Alex 从事网络数据分析工作,Python 依然在他的工作中扮演着重要角色。他经常写博客,分享工作和个人项目,通常这些项目围绕数据展开,并且通常涉及 Python 和 Jupyter Notebooks。
目标
-
快速启动 Jupyter 生态系统
-
确定潜在的研究领域并进行探索性数据分析
-
规划机器学习分类策略并训练分类模型
-
使用验证曲线和降维技术来调优和增强你的模型
-
从网页抓取表格数据并将其转换为 Pandas DataFrame
-
创建交互式、适用于网络的可视化图表,以清晰地传达你的发现
目标读者
《应用数据科学:Python 和 Jupyter》非常适合来自各行各业的专业人士,鉴于数据科学的流行和普及。你需要具备一定的 Python 使用经验,任何涉及 Pandas、Matplotlib 和 Pandas 等库的工作经验都将帮助你更好地入门。
方法
《应用数据科学:Python 和 Jupyter》涵盖了标准数据工作流程的各个方面,完美结合了理论、实际编程操作和生动的示例插图。每个模块都是建立在前一章内容的基础上的。本书包含多个使用实际商业场景的活动,让你在高度相关的情境中实践并应用新学到的技能。
最低硬件要求
最低硬件要求如下:
-
处理器:Intel i5(或同等配置)
-
内存:8 GB RAM
-
硬盘:10 GB
-
互联网连接
软件要求
你还需要提前安装以下软件:
-
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 Notebook 的 Anaconda 环境。
安装 Anaconda
-
在浏览器中访问 https://www.anaconda.com/download/。
-
根据你使用的操作系统,点击 Windows、Mac 或 Linux。
-
接下来,点击下载选项。确保下载最新版本。
-
下载后打开安装程序。
-
按照安装程序中的步骤操作,完成后即可!你的 Anaconda 发行版已经准备好。
更新 Jupyter 并安装依赖项
-
搜索 Anaconda Prompt 并打开它。
-
输入以下命令以更新 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 Prompt 打开 Jupyter Notebook,请使用以下命令:
jupyter notebook pip install -U scikit-learn
额外资源
本书的代码包也托管在 GitHub 上,网址为github.com/TrainingByPackt/Applied-Data-Science-with-Python-and-Jupyter
。
我们还有来自丰富书籍和视频目录的其他代码包,网址为 https://github.com/PacktPublishing/。快去看看吧!
约定
文本中的代码单词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名显示如下:
“最后,图像会保存为高分辨率 PNG 格式到figures
文件夹。”
代码块如下设置:
y = df['MEDV'].copy()
del df['MEDV']
df = pd.concat((y, df), axis=1)
任何命令行输入或输出都如下所示:
jupyter notebook
新术语和重要单词以粗体显示。你在
屏幕上的内容,例如菜单或对话框中的内容,以如下方式显示:“点击右上角的新建,并从下拉菜单中选择一个内核。”
1
第一章:Jupyter 基础
学习目标
到本章结束时,你将能够:
-
描述 Jupyter Notebooks 及其如何用于数据分析
-
描述 Jupyter Notebooks 的特点
-
使用 Python 数据科学库
-
执行简单的探索性数据分析
在本章中,您将通过完成几个动手实践练习,学习并实现 Jupyter Notebook 的基本功能。
介绍
Jupyter Notebooks 是 Python 数据科学家最重要的工具之一。这是因为它们是开发可复现的数据分析管道的理想环境。数据可以在同一个 Notebook 中加载、转换和建模,在这个过程中,测试代码和探索想法变得又快又简单。此外,所有这些都可以使用格式化文本进行“内联”文档记录,这样你可以为自己做笔记,甚至生成结构化报告。
其他类似的平台——例如 RStudio 或 Spyder——向用户提供多个窗口,这样会增加诸如复制粘贴代码和重新执行已运行代码等繁琐任务的难度。这些工具通常还涉及 读取评估提示循环(REPLs),其中代码在一个保存了内存的终端会话中运行。这种开发环境不利于可复现性,也不适合开发。Jupyter Notebooks 通过提供一个单一窗口来解决所有这些问题,在该窗口中,代码片段被执行,输出结果会内联显示。这使得用户可以高效地开发代码,并能够回顾之前的工作,作为参考或进行修改。
我们将从解释 Jupyter Notebooks 的真正含义开始,并继续讨论它们为何在数据科学家中如此受欢迎。接下来,我们将一起打开一个 Notebook,进行一些练习,学习如何使用该平台。最后,我们将深入到我们的第一个分析,并进行探索性分析。
基本功能和特点
在本节中,我们首先通过示例和讨论演示 Jupyter Notebooks 的实用性。然后,为了覆盖 Jupyter Notebooks 的基础知识,我们将展示如何启动和使用该平台,帮助初学者理解其基本用法。对于那些曾经使用过 Jupyter Notebooks 的人来说,这将主要是一次复习;不过,你也一定会在本主题中看到一些新的内容。
什么是 Jupyter Notebook,为什么它有用?
Jupyter Notebooks 是本地运行的 Web 应用程序,包含实时代码、方程式、图形、交互式应用程序以及 Markdown 文本。标准编程语言是 Python,这也是本书中使用的语言;然而,请注意,它也支持多种其他语言,包括数据科学领域的另一大主流语言 R:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_01.jpg
图 1.1:Jupyter Notebook 示例工作簿
熟悉 R 的人应该知道 R Markdown。README.md
Markdown 文件。这种格式适用于基本的文本格式化。它类似于 HTML,但允许的自定义选项较少。
Markdown 中常用的符号包括井号(#)用来创建标题,方括号和圆括号用来插入超链接,星号用来创建斜体或粗体文本:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_02.jpg
图 1.2:示例 Markdown 文档
在了解了 Markdown 的基础知识后,让我们回到 R Markdown,其中 Markdown 文本可以与可执行代码一起编写。Jupyter Notebooks 为 Python 提供了等效的功能,尽管正如我们将看到的,它们的工作方式与 R Markdown 文档有很大不同。例如,R Markdown 假设你是在写 Markdown,除非另有说明,而 Jupyter Notebooks 假设你输入的是代码。这使得 Jupyter Notebooks 更适合用于快速开发和测试。
从数据科学的角度来看,Jupyter Notebook 主要有两种类型,取决于其使用方式:实验室风格和交付式。
实验室风格 Notebooks 是用来作为编程版的研究期刊。这些 Notebooks 应包含你加载、处理、分析和建模数据的所有工作。其理念是记录下你做过的每一步,以便将来参考,因此通常不建议删除或修改之前的实验室风格 Notebooks。同时,随着分析的推进,积累多个带有时间戳的 Notebook 版本也是一个好主意,这样你就可以在需要时回顾之前的状态。
交付式 Notebooks 旨在呈现内容,应该仅包含实验室风格 Notebooks 的一部分内容。例如,这可以是一个有趣的发现,供你与同事分享;也可以是为经理准备的深入分析报告,或是为利益相关者总结的关键发现。
无论是哪种情况,一个重要的概念是可重现性。如果你在记录软件版本时很细心,那么任何收到报告的人都可以重新运行 Notebook 并计算出与你相同的结果。在科学界,可重现性变得越来越困难,这无疑是一个令人耳目一新的做法。
导航平台
现在,我们将打开一个 Jupyter Notebook,开始学习其界面。在这里,我们假设你对该平台没有任何先前的了解,并将讲解基本的使用方法。
练习 1:介绍 Jupyter Notebooks
-
在终端中导航到配套材料目录
注意
在 Unix 系统如 Mac 或 Linux 上,可以使用
ls
显示目录内容,使用cd
切换目录。在 Windows 系统上,使用dir
显示目录内容,使用cd
切换目录。如果你想将驱动器从 C: 切换到 D:,可以执行 d: 来切换驱动器。 -
在终端中输入以下命令以启动新的本地 Notebook 服务器:
jupyter notebook
默认浏览器将会打开一个新的窗口或标签页,显示工作目录中的 Notebook 仪表板。在这里,你将看到其中包含的文件夹和文件列表。
-
点击一个文件夹以导航到那个特定的路径,然后点击一个文件打开它。虽然它的主要用途是编辑 IPYNB Notebook 文件,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
来确认。也请关闭网页浏览器窗口。 -
运行以下代码加载可用选项列表:
jupyter notebook --help
-
通过运行以下命令,在本地端口
9000
打开NotebookApp
:jupyter notebook --port 9000
-
在 Jupyter 仪表板的右上角点击新建,然后从下拉菜单中选择一个内核(即,在Notebooks部分选择一个):https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_03.jpg
图 1.3:从下拉菜单中选择一个内核
这是创建新 Jupyter Notebook 的主要方法。
内核为 Notebook 提供编程语言支持。如果你通过 Anaconda 安装了 Python,那么该版本应为默认内核。Conda 虚拟环境也将在这里可用。
注意
虚拟环境是管理同一台机器上多个项目的一个很好的工具。每个虚拟环境可能包含不同版本的 Python 和外部库。Python 内置有虚拟环境;然而,Conda 虚拟环境与 Jupyter Notebooks 的集成更好,并且有其他一些优点。文档可以在此查看:
conda.io/docs/user-guide/tasks/manage-environments.html
。 -
在新创建的空白 Notebook 中,点击顶部的单元格并输入
print('hello world')
,或者任何其他会输出到屏幕的代码片段。 -
点击单元格并按Shift + Enter,或者选择
stdout
或stderr
输出,代码运行后会在下面显示结果。此外,最后一行写入的对象的字符串表示也会显示出来。这非常方便,特别是用于显示表格,但有时我们不希望显示最后的对象。在这种情况下,可以在行尾添加分号(;)来抑制显示。新单元格默认期望并运行代码输入;不过,它们也可以改为渲染Markdown。 -
点击一个空白单元格并将其更改为接受 Markdown 格式的文本。这可以通过工具栏中的下拉菜单图标或通过在单元格菜单中选择Markdown来完成。在这里写一些文本(任何文本都可以),确保使用 Markdown 格式符号,如 #。
-
滚动到工具栏中的播放图标:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_04.jpg
图 1.4:Jupyter Notebook 工具栏
这可以用来运行单元格。然而,正如我们稍后所看到的,使用键盘快捷键Shift + Enter来运行单元格更为方便。
紧挨着这个按钮的是停止图标,可以用来停止单元格的运行。例如,如果某个单元格运行得太慢时,这个功能会非常有用:
](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_05_2.jpg)
图 1.5:Jupyter Notebook 中的停止图标
可以通过插入菜单手动添加新单元格:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_06.jpg
图 1.6:在 Jupyter Notebook 中通过插入菜单添加新单元格
单元格可以使用图标或通过在编辑菜单中选择选项来复制、粘贴和删除:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_07.jpg
图 1.7:Jupyter Notebook 中的编辑菜单
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_08.jpg
图 1.8:在 Jupyter Notebook 中剪切和复制单元格
单元格也可以通过这种方式上下移动:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_09.jpg
图 1.9:在 Jupyter Notebook 中上下移动单元格
在单元格菜单下有一些有用的选项,可以运行一组单元格或整个 Notebook:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_10.jpg
图 1.10:在 Jupyter Notebook 中运行单元格
尝试使用工具栏选项来移动单元格、插入新单元格和删除单元格。关于这些 Notebook 需要理解的一个重要事项是单元格之间共享内存。其实很简单:每个存在于表单上的单元格都可以访问全局变量集。例如,在一个单元格中定义的函数可以从任何其他单元格中调用,变量也是如此。正如预期的那样,函数范围内的任何内容都不会是全局变量,只能在该特定函数内访问。
-
打开内核菜单以查看选项。内核菜单对于停止脚本执行以及在内核崩溃时重启 Notebook 很有用。内核也可以随时在这里切换,但由于可重复性问题,不建议为一个 Notebook 使用多个内核。
-
打开文件菜单以查看选项。文件菜单包含将 Notebook 下载为各种格式的选项。特别是,建议保存 Notebook 的 HTML 版本,在该版本中内容静态呈现,并且可以像在网页浏览器中一样打开和查看。
Notebook 的名称将在左上角显示。新的 Notebook 会自动命名为 未命名。
-
点击左上角当前名称,修改你的 IPYNB Notebook 文件名,并输入新名称。然后保存文件。
-
关闭当前浏览器标签页(退出 Notebook),然后转到应该仍然打开的 Jupyter 仪表盘 标签页。(如果没有打开,可以通过复制并粘贴终端中的 HTTP 链接来重新加载它。)
由于我们没有关闭 Notebook,而且只是保存并退出,它将在 Jupyter 仪表盘的 文件 部分的文件名旁边显示绿色书本符号,并且在右侧的最后修改日期旁边标注为 运行中。Notebook 可以从这里关闭。
-
通过选择你正在工作的 Notebook(勾选名称左侧的复选框),然后点击橙色的 关闭 按钮来退出 Notebook:
注
阅读基础的键盘快捷键并进行测试。
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_11.jpg
](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_11.jpg)
图 1.11:关闭 Jupyter Notebook
注
如果你打算花很多时间使用 Jupyter Notebook,学习键盘快捷键是很值得的。这将显著加快你的工作流程。特别有用的命令是手动添加新单元的快捷键,以及将单元从代码转换为 Markdown 格式的快捷键。点击 帮助 菜单中的 键盘快捷键 查看如何操作。
Jupyter 功能
Jupyter 拥有许多吸引人的功能,使 Python 编程更加高效。这些功能包括从查看文档字符串到执行 Bash 命令的各种方法。我们将在本节中探索其中的一些功能。
注
官方的 IPython 文档可以在这里找到:ipython.readthedocs.io/en/stable/
。其中包含我们将在这里讨论的功能及其他内容的详细信息。
练习 2:实现 Jupyter 最有用的功能
-
从 Jupyter 仪表盘中导航到
lesson-1
目录,并通过选择打开lesson-1-workbook.ipynb
。Jupyter Notebook 的标准文件扩展名是
.ipynb
,它是在 Jupyter 还被称为 IPython Notebook 时引入的。 -
滚动到 Jupyter Notebook 中的
Subtopic C: Jupyter Features
部分。我们首先回顾基本的键盘快捷键。它们特别有助于避免频繁使用鼠标,从而大大加快工作流程。
你可以通过在任何对象后面加上问号并运行单元来获取帮助。Jupyter 会找到该对象的文档字符串,并在应用程序底部弹出窗口中显示它。
-
运行 获取帮助 单元,查看 Jupyter 如何在 Notebook 底部显示文档字符串。在此部分添加一个单元,并获取你选择的对象的帮助:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_12.jpg
图 1.12:在 Jupyter Notebook 中获取帮助
-
点击 Tab 完成 部分的一个空白代码单元格。输入 import(包括后面的空格),然后按下 Tab 键:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_13.jpg
图 1.13:Jupyter Notebook 中的 Tab 完成
上述操作列出了所有可供导入的模块。
Tab 完成功能可以用于以下场景:列出导入外部库时可用的模块;列出导入的外部库中的可用模块;函数和变量的自动补全。当你需要了解模块的可用输入参数,探索新库,发现新模块,或者简单地加速工作流程时,这尤其有用。它们可以节省编写变量名或函数的时间,减少因拼写错误导致的 bug。Tab 完成功能如此强大,以至于今天之后,你可能在其他编辑器中编写 Python 代码时会遇到困难!
-
滚动到 Jupyter 魔法函数部分,并运行包含
%lsmagic
和%matplotlib inline
的单元格:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_14.jpg图 1.14:Jupyter 魔法函数
百分号
%
和%%
是 Jupyter Notebook 的基本功能之一,称为魔法命令。以%%
开头的魔法命令会应用于整个单元格,而以%
开头的魔法命令只会应用于该行。%lsmagic
列出了可用的选项。我们将讨论并展示一些最有用的示例。你可能最常见的魔法命令是%matplotlib inline
,它允许在 Jupyter Notebook 中显示 matplotlib 图形,而无需显式使用plt.show()
。定时功能非常实用,有两种类型:标准计时器(
%time
或%%time
)和测量多个迭代平均运行时间的计时器(%timeit
和%%timeit
)。注意
请注意,列表推导在 Python 中比循环更快。这可以通过比较第一个和第二个单元格的墙时间来看,其中相同的计算在列表推导中显著更快。
-
运行
pwd
的单元格,查看目录中的内容(ls
),创建新文件夹(mkdir
),以及写入文件内容(cat
/head
/tail
)。 -
在笔记本的 使用 bash 部分运行第一个单元格。
该单元格会将一些文本写入工作目录中的文件,打印目录内容,打印空行,然后写回新创建的文件内容并删除该文件:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_15.jpg
图 1.15:在 Jupyter Notebook 中使用 Bash
-
运行仅包含
ls
和pwd
的单元格。请注意,我们无需显式使用 Bash 魔法命令,这些命令仍然可以正常工作。还有很多可以安装的外部魔法命令。一个流行的魔法命令是
ipython-sql
,它允许在单元格中执行 SQL 代码。 -
打开一个新的终端窗口,并执行以下代码来安装 ipython-sql:
pip install ipython-sql
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_16.jpg
](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_16.jpg)
图 1.16:通过 pip 安装 ipython-sql
-
运行
%load_ext sql
单元格将外部命令加载到 Notebook 中:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_17.jpg图 1.17:在 Jupyter Notebook 中加载 sql
这允许连接到远程数据库,以便可以在 Notebook 中直接执行(并因此记录)查询。
-
运行包含 SQL 示例查询的单元格:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_18.jpg
图 1.18:运行示例 SQL 查询
在这里,我们首先连接到本地 sqlite 源;然而,这行代码也可以指向本地或远程服务器上的特定数据库。然后,我们执行一个简单的
SELECT
查询,展示如何将单元格转换为运行 SQL 代码而非 Python 代码。 -
现在从终端使用
pip
安装版本文档工具。打开一个新窗口并运行以下代码:pip install version_information
安装完成后,可以通过
%load_ext version_information
将其导入到任何 Notebook 中。最后,一旦加载,它可以用来显示 Notebook 中每个软件的版本信息。%version_information
命令有助于文档编写,但它并不是 Jupyter 的标准功能。就像我们刚才看到的 SQL 示例一样,可以通过pip
从命令行安装。 -
运行加载并调用
version_information
命令的单元格:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_19.jpg
图 1.19:Jupyter 中的版本信息
将 Jupyter Notebook 转换为 Python 脚本
你可以将 Jupyter Notebook 转换为 Python 脚本。这相当于将每个代码单元格的内容复制并粘贴到一个.py
文件中。Markdown 部分也将作为注释包含其中。
转换可以通过NotebookApp
或命令行完成,如下所示:
jupyter nbconvert --to=python lesson-1-notebook.ipynb
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_20.jpg
图 1.20:将 Jupyter Notebook 转换为 Python 脚本
这在例如当你想使用pipreqs
等工具来确定 Notebook 的库需求时非常有用。该工具确定项目中使用的库并将其导出到requirements.txt
文件中(可以通过运行pip install pipreqs
来安装)。
命令从包含.py
文件的文件夹外部调用。例如,如果.py
文件位于名为lesson-1
的文件夹中,可以执行以下操作:
pipreqs lesson-1/
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_21.jpg
图 1.21:使用 pipreqs 确定库需求
对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
Python 库
在了解了 Jupyter Notebooks 的所有基础知识,甚至一些更高级的功能后,我们将把注意力转向本书中将使用的 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 还提供了各种数学和数据处理功能。
-
NaN
条目和计算数据的统计描述。本书将重点介绍 Pandas DataFrame 的使用。 -
Matplotlib 是一个受 MATLAB 平台启发的绘图工具。熟悉 R 的人可以将其视为 Python 版本的 ggplot。它是最流行的 Python 绘图库,允许高度的自定义。
-
Seaborn 是 matplotlib 的扩展,其中包括了许多对于数据科学非常有用的绘图工具。一般来说,这使得分析工作比使用像 matplotlib 和 scikit-learn 这样的库手动创建相同的图形要更快。
-
scikit-learn 是最常用的机器学习库。它提供了顶级的算法和非常优雅的 API,其中模型被实例化后,使用数据进行 拟合。它还提供数据处理模块和其他对于预测分析有用的工具。
-
Requests 是进行 HTTP 请求的首选库。它使得从网页获取 HTML 内容以及与 API 进行交互变得非常简单。对于 HTML 的解析,许多人选择使用 BeautifulSoup4,这本书中也会涉及到它。
-
Bokeh 是一个交互式可视化库。它的功能类似于 matplotlib,但允许我们向图表中添加悬停、缩放、点击等交互工具。它还允许我们在 Jupyter Notebook 中渲染和操作图表。
介绍完这些库之后,让我们回到 Notebook,并通过运行 import
语句来加载它们。这将引导我们进入第一次分析,开始使用数据集。
练习 3:导入外部库并设置绘图环境
-
打开
lesson 1
Jupyter Notebook,并滚动到Subtopic D: Python Libraries
部分。就像常规的 Python 脚本一样,库可以随时导入到 Notebook 中。最好的做法是将大多数使用的包放在文件的顶部。有时,在 Notebook 中间加载库也是有意义的,完全没问题。
-
运行单元格以导入外部库并设置绘图选项:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_22.jpg
图 1.22:导入 Python 库
对于一个良好的 Notebook 设置,通常将各种选项和导入放在顶部非常有用。例如,下面的代码可以运行,改变图形的外观,使其看起来比 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 Notebook 进行数据科学的基础知识。我们从探索平台并熟悉界面开始。接着,我们讨论了最有用的特性,包括选项卡补全和魔法函数。最后,我们介绍了本书中将要使用的 Python 库。
接下来的部分将非常互动,我们将一起使用 Jupyter Notebook 进行第一次分析。
我们的第一次分析 - 波士顿房价数据集
到目前为止,本章已集中介绍了 Jupyter 的特性和基本使用方法。现在,我们将付诸实践,进行一些数据探索和分析。
本节我们将查看的 数据集 是所谓的波士顿房价数据集。它包含了有关波士顿市区各个地区的美国人口普查数据。每个样本对应一个独特的地区,并且有大约十几个测量值。我们应该把样本看作行,把测量值看作列。该数据集首次发布于 1978 年,非常小,只有大约 500 个样本。
既然我们已经了解了数据集的背景,接下来让我们决定一个大致的探索和分析计划。如果适用,这个计划将包括相关的研究问题。在这种情况下,目标不是回答一个问题,而是展示 Jupyter 的实际应用,并说明一些基本的数据分析方法。
我们对这个分析的总体方法将是:
-
使用 Pandas DataFrame 将数据加载到 Jupyter 中
-
定量理解特征
-
寻找模式并生成问题
-
回答问题以解决问题
使用 Pandas DataFrame 将数据加载到 Jupyter 中
数据通常以表格形式存储,这意味着它可以保存为逗号分隔值(CSV)文件。此格式和其他许多格式可以通过 Pandas 库读取为 DataFrame 对象。其他常见的格式包括制表符分隔变量(TSV)、SQL 表格和 JSON 数据结构。事实上,Pandas 支持所有这些格式。不过,在本示例中,我们不打算通过这种方式加载数据,因为数据集可以直接通过 scikit-learn 获得。
注意
加载数据进行分析后的一个重要部分是确保数据的清洁。例如,我们通常需要处理缺失数据,并确保所有列的数据类型正确。本节中使用的数据集已经清理过,因此我们不需要担心这一点。然而,在第二章中我们将看到更混乱的数据,并探索处理这些数据的技术。
练习 4:加载波士顿房价数据集
-
滚动到 Jupyter Notebook 第一章中
Topic B: 我们的第一次分析:波士顿房价数据集
的Subtopic A
部分。可以通过
sklearn.datasets
模块使用load_boston
方法访问波士顿房价数据集。 -
运行本节中的前两个单元格以加载波士顿数据集,并查看
datastructures
类型:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_23.jpg](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_23.jpg)
图 1.23:加载波士顿数据集
第二个单元格的输出告诉我们它是一个 scikit-learn 的
Bunch
对象。让我们进一步了解它,以便理解我们正在处理的内容。 -
运行下一个单元格以从 scikit-learn
utils
导入基础对象,并在我们的笔记本中打印文档字符串:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_24.jpg](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_24.jpg)
图 1.24:导入基础对象并打印文档字符串
-
通过运行下一个单元格打印字段名称(即字典的键)。我们发现这些字段是自解释的:
['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
。我们将在分析过程中以此为参考。注意
完整代码请参考以下链接:
bit.ly/2EL11cW
现在,我们将创建一个包含数据的 Pandas DataFrame。这样做有几个好处:所有数据都将存储在一个对象中,我们可以使用有用且计算效率高的 DataFrame 方法,此外,像 Seaborn 这样的其他库也能很好地与 DataFrame 集成。
在这种情况下,我们将使用标准构造方法创建 DataFrame。
-
运行导入 Pandas 并检索
pd.DataFrame
文档字符串的单元格:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_25.jpg图 1.25:检索 pd.DataFrame 的文档字符串
文档字符串揭示了 DataFrame 输入参数。我们希望将
boston['data']
作为数据输入,并使用boston['feature_names']
作为列名。 -
运行接下来的几个单元格,打印数据、其形状和特征名:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_26.jpg
图 1.26:打印数据、形状和特征名
从输出中,我们看到数据是一个二维的 NumPy 数组。运行命令
boston['data'].shape
会返回长度(样本数)和特征数量,分别作为第一个和第二个输出。 -
通过运行以下代码将数据加载到 Pandas DataFrame
df
中:df = pd.DataFrame(data=boston['data'], columns=boston['feature_names'])
在机器学习中,被建模的变量称为目标变量;它是你试图根据特征预测的内容。对于这个数据集,建议的目标是 MEDV,即以千美元为单位的房屋中位数价格。
-
运行下一个单元格查看目标的形状:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_27.jpg
图 1.27:查看目标形状的代码
我们看到它的长度与特征相同,这是我们预期的。因此,可以将其作为新列添加到 DataFrame 中。
-
通过运行以下代码将目标变量添加到 df 中:
df['MEDV'] = boston['target']
-
通过运行以下代码,将目标变量移到
df
的前面:y = df['MEDV'].copy() del df['MEDV'] df = pd.concat((y, df), axis=1)
这样做是为了通过将目标存储在 DataFrame 的前面,区分目标和特征。
在这里,我们引入一个虚拟变量
y
来保存目标列的副本,然后将其从 DataFrame 中移除。接着,我们使用 Pandas 的连接函数将其与剩余的 DataFrame 沿着第 1 轴(而不是第 0 轴,即行方向)连接。注意
你将经常看到使用点表示法来引用 DataFrame 的列。例如,之前我们可以使用
y = df.MEDV.copy()
。然而,这种方法无法删除列;del df.MEDV
会引发错误。 -
实现
df.head()
或df.tail()
来查看数据,使用len(df)
来验证样本数量是否符合预期。运行接下来的几个单元格查看df
的头部、尾部和长度:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_28.jpg图 1.28:打印数据框 df 的前几行
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_29.jpg
图 1.29:打印数据框 df 的尾部
每一行都有一个索引值,如表格左侧加粗显示的数字。默认情况下,这些索引是从 0 开始的整数,并且每行递增 1。
-
打印
df.dtypes
会显示每一列所包含的数据类型。运行下一个单元格查看每列的数据类型。对于这个数据集,我们看到每个字段都是浮动类型,因此很可能是连续变量,包括目标变量。这意味着预测目标变量是一个回归问题。 -
运行
df.isnull()
来清理数据集,因为 Pandas 会自动将缺失数据设置为NaN
值。要获取每列的NaN
值数量,可以执行df.isnull().sum()
:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_30.jpg图 1.30:通过识别 NaN 值清理数据集
df.isnull()
返回一个与df
长度相同的布尔值框架。对于这个数据集,我们看到没有
NaN
值,这意味着我们不需要立即清理数据,可以继续进行后续操作。 -
通过运行包含以下代码的单元格来移除一些列:
for col in ['ZN', 'NOX', 'RAD', 'PTRATIO', 'B']: del df[col]
这样做是为了简化分析。接下来,我们将更详细地关注剩余的列。
数据探索
由于这是一个我们之前从未见过的新数据集,首要任务是理解数据。我们已经看到过数据的文本描述,这是理解数据的定性信息。接下来,我们将进行定量描述。
练习 5:分析波士顿住房数据集
-
在 Jupyter Notebook 中导航到
子主题 B:数据探索
,并运行包含df.describe()
的单元格:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_31.jpg图 1.31:统计属性的计算及输出
这会计算每列的各种统计属性,包括均值、标准差、最小值和最大值。这个表格提供了一个关于所有数据分布的总体概念。请注意,我们通过在输出结果后添加
.T
来转换结果,这会交换行和列。在继续分析之前,我们将指定一组重点关注的列。
-
运行包含定义“重点列”内容的单元格:
cols = ['RM', 'AGE', 'TAX', 'LSTAT', 'MEDV']
-
通过运行
df[cols].head()
来显示上述数据框的子集:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_32.jpg图 1.32:显示重点列
为了提醒自己,我们回顾一下每一列的含义。根据数据集文档,我们有以下信息:
- 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()
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_33.jpg
图 1.33:成对计算相关性
这个结果表格显示了每一组值之间的相关性得分。较大的正得分表示强正相关(即方向相同)。如预期所示,我们在对角线看到最大值为 1。
默认情况下,Pandas 会计算每对值的标准相关系数,这也叫做 Pearson 系数。其定义为两个变量的协方差除以它们标准差的乘积:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_56.jpg
协方差的定义如下:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_57.jpg
这里,n 是样本数量,xi 和 yi 是被求和的单个样本,X 和 Y 分别是每组数据的均值。
与其使劲眯眼去看前面的表格,使用热图来可视化数据会更为直观。这可以通过 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)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_34.jpg
图 1.34:所有变量的热图绘制
我们调用 sns.heatmap
并将配对相关矩阵作为输入。我们使用自定义的色彩调色板来覆盖 Seaborn 的默认设置。该函数返回一个 matplotlib.axes
对象,并由变量 ax
引用。
最终的图表会以高分辨率 PNG 格式保存到 figures
文件夹中。
在我们数据集探索的最后一步,我们将使用 Seaborn 的 pairplot
函数来可视化数据。
使用 Seaborn 的 pairplot
函数可视化数据框。运行以下代码所在的单元格:
sns.pairplot(df[cols],
plot_kws={'alpha': 0.6},
diag_kws={'bins': 30})
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_35.jpg
图 1.35:使用 Seaborn 进行数据可视化
注意
请注意,无监督学习技术超出了本书的讨论范围。
查看对角线上的直方图,我们可以看到以下内容:
a:RM 和 MEDV 的分布形态最接近正态分布。
b:AGE 向左偏斜,LSTAT 向右偏斜(这可能看起来违反直觉,但偏斜是指均值相对于最大值的位置)。
c:对于 TAX,我们发现分布的大部分集中在 700 左右。这一点从散点图中也能看出来。
详细查看 df.describe()
后,MDEV 的最小值和最大值分别为 5k 和 50k。这表明数据集中中位数房价的上限为 50k。
使用 Jupyter Notebooks 进行预测分析简介
继续分析波士顿住房数据集,我们可以看到它为我们提供了一个回归问题,要求根据一组特征预测连续的目标变量。特别地,我们将预测中位数房价(MEDV)。
我们将训练只使用一个特征作为输入的模型来进行预测。通过这种方式,模型在概念上会比较简单,便于理解,我们可以更加专注于 scikit-learn API 的技术细节。然后,在下一章,你将能更轻松地处理相对复杂的模型。
练习 6:使用 Seaborn 和 Scikit-learn 应用线性模型
-
滚动到 Jupyter Notebook 中的
子话题 C: 预测分析简介
,并查看上方我们在前一部分中创建的 pairplot。特别是,查看左下角的散点图:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_36.jpg图 1.36:MEDV 和 LSTAT 的散点图
请注意,每个房屋的房间数 (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}))
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_37.jpg
图 1.37:使用线性模型绘制散点图
最佳拟合线是通过最小化普通最小二乘误差函数计算得出的,这是 Seaborn 在我们调用
regplot
函数时自动完成的。还要注意线条周围的阴影区域,它们表示 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('')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_38.jpg
图 1.38:使用 Seaborn 绘制残差图
这些残差图上的每个点代表该样本 (
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()
注意
完整代码请参见以下链接:
bit.ly/2JgPZdU
在
get_mse
函数中,我们首先将变量y
和x
分别分配给目标 MDEV 和自变量特征。这些变量通过调用values
属性被转换为 NumPy 数组。自变量特征数组被重塑为 scikit-learn 期望的格式;当建模一维特征空间时,只有在这种情况下才需要重塑。然后,模型被实例化并在数据上进行拟合。对于线性回归,拟合过程包括使用普通最小二乘法计算模型参数(最小化每个样本的误差平方和)。最后,在确定参数后,我们预测目标变量并使用结果计算MSE。 -
通过运行包含以下内容的单元格,调用
get_mse
函数来处理RM和LSTAT:get_mse(df, 'RM') get_mse(df, 'LSTAT')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_39.jpg
图 1.39:调用get_mse
函数计算 RM 和 LSTAT
比较MSE,结果显示LSTAT的误差略低。然而,回顾散点图,我们可能会发现使用多项式模型对LSTAT进行建模会更成功。在接下来的活动中,我们将通过使用 scikit-learn 计算一个三次多项式模型来验证这一点。
让我们暂时忘记波士顿住房数据集,考虑另一个可能会用到多项式回归的实际情况。以下是一个建模天气数据的示例。在接下来的图表中,我们可以看到温哥华(加拿大 BC 省)的温度(线条)和降水量(条形):
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_40.jpg
图 1.40:可视化温哥华(加拿大)的天气数据
这些字段中的任何一个都可能非常适合由四次多项式进行拟合。如果你对预测某个连续日期范围内的温度或降水量感兴趣,那么这个模型会非常有价值。
注意
你可以在此找到数据源:climate.weather.gc.ca/climate_normals/results_e.
html?stnID=888。
活动 1:构建三次多项式模型
将注意力重新转回波士顿住房数据集,我们希望构建一个三次多项式模型来与线性模型进行比较。回想一下我们要解决的实际问题:给定低收入人口的百分比,预测中位数房价。这个模型对潜在的波士顿购房者会有帮助,尤其是那些关心自己社区低收入人口比例的人。
我们的目标是使用 scikit-learn 拟合一个多项式回归模型,以预测中位数房屋价值(MEDV),给定LSTAT值。我们希望构建一个具有较低均方误差(MSE)的模型。为了实现这一目标,需要执行以下步骤:
-
滚动到
Subtopic C
下方 Jupyter Notebook 中的空单元格。这些单元格位于线性模型Activity
标题下方。注意
在我们完成活动时,您应该使用代码填充这些空单元格。随着单元格填充,您可能需要插入新的单元格;请根据需要进行操作。
-
从
df
中提取我们的依赖特征和目标变量。 -
通过打印前三个样本,验证
x
的样子。 -
通过从 scikit-learn 导入适当的转换工具,将
x
转换为“多项式特征”。 -
通过运行
fit_transform
方法并构建多项式特征集,转换x
。 -
通过打印前几个样本,验证
x_poly
的样子。 -
导入
LinearRegression
类,并像计算 MSE 时一样构建我们的线性分类模型。 -
提取系数并打印多项式模型。
-
确定每个样本的预测值并计算残差。
-
打印一些残差值。
-
打印三阶多项式模型的 MSE。
-
绘制多项式模型及其样本。
-
绘制残差。
注意
详细步骤和解决方案见附录 A(第 144 页)。
成功使用多项式模型建模数据后,让我们通过查看分类特征来结束这一章。特别是,我们将构建一组分类特征,并使用它们更详细地探索数据集。
使用分类特征进行分段分析
通常,我们会遇到包含连续字段和分类字段的数据集。在这种情况下,我们可以通过将连续变量与分类字段结合来学习数据并发现模式。
作为一个具体示例,假设你正在评估广告活动的投资回报率。你能访问的数据包含一些计算得到的投资回报率(ROI)指标。这些值是每日计算并记录的,你正在分析去年的数据。你的任务是从数据中获取可操作的见解,找出改进广告活动的方法。在查看 ROI 的每日时间序列时,你会看到数据中的每周波动。通过按星期几分段,你发现了以下 ROI 分布(其中 0 代表一周的第一天,6 代表最后一天)。
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_48.jpg
图 1.41:投资回报率的示例小提琴图
由于我们正在使用的波士顿住房数据集中没有任何类别字段,我们将通过有效地离散化一个连续字段来创建一个。在我们的例子中,这将涉及将数据分为“低”、“中”和“高”三个类别。需要注意的是,我们并非单纯地创建一个类别数据字段来说明本节中的数据分析概念。正如我们将看到的那样,做这件事可以揭示一些数据中的洞察,这些洞察否则可能会难以察觉或完全无法获取。
练习 7:从连续变量创建类别字段并制作分段可视化
-
向上滚动到 Jupyter Notebook 中的 pairplot,我们比较了MEDV、LSTAT、TAX、AGE和RM:!
](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_49.jpg)
图 1.42:MEDV、LSTAT、TAX、AGE 和 RM 的图表比较
看一下包含AGE(年龄)的面板。提醒一下,这个特征被定义为1940 年之前建成的业主自住单元的比例。我们将把这个特征转换为一个类别变量。转换后,我们将能够重新绘制这个图,每个面板根据年龄类别通过颜色进行分段。
-
向下滚动到
Subtopic D: Building and exploring categorical features
并点击第一个单元格。输入并执行以下命令以绘制kde_kws={'lw': 0}
,以跳过前图中的核密度估计图。看这个图,低AGE(年龄)样本非常少,而AGE较大的样本则多得多。这可以从分布在最右侧的陡峭度看出来。
红线表示分布中的 1/3 和 2/3 点。观察分布与这些水平线的交点,我们可以看到,只有大约 33%的样本AGE小于 55,33%的样本AGE大于 90!换句话说,三分之一的住宅区有不到 55%的房屋是在 1940 年之前建成的。这些可以视为相对较新的社区。另一方面,另三分之一的住宅区有超过 90%的房屋是在 1940 年之前建成的。这些被认为是非常老的社区。我们将使用红色水平线与分布交点的位置作为指南,将该特征分为:相对较新、相对较旧和非常旧三个类别。
-
创建一个新的类别特征,并通过运行以下代码设置分割点:
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
,它应该接受表示数据行的一个参数,并为新列返回一个值。在这种情况下,传递的数据行只是一个单独的值,pd.Series.str
可以更快地完成同样的事情。因此,建议避免使用它,特别是在处理大型数据集时。在即将来临的章节中,我们将看到一些矢量化方法的示例。 -
通过在新的单元格中键入
df.groupby('AGE_category').size()
并运行来验证我们已经分组到每个年龄类别中的样本数:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_51.jpg图 1.44:验证变量的分组
从结果来看,可以看到两个类大小相当,而
AGE_category
。 -
运行以下代码构建小提琴图:
sns.violinplot(x='MEDV', y='AGE_category', data=df, order=['Relatively New', 'Relatively Old', 'Very Old']);
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_52.jpg
图 1.45:AGE_category 和 MEDV 的小提琴图
小提琴图显示了每个年龄类别中位房屋价值分布的核密度估计。我们看到它们都类似于正态分布。非常老的组包含最低的中位房屋价值样本,并具有相对较大的宽度,而其他组则更紧密地围绕它们的平均值。年轻组偏向于高端,这可以从分布体内的厚黑线中的白色点的右侧扩展和位置看出。
这个白色点表示均值,而厚黑线大致跨越了人口的 50%(填充到白色点两侧的第一个四分位数)。细黑线表示箱线图的须,跨越了人口的 95%。可以通过向
sns.violinplot()
传递inner='point'
来修改这个内部可视化,我们现在来做一下。 -
重新构建小提琴图,在
sns.violinplot
调用中添加inner='point'
参数:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_53.jpg图 1.46:AGE_category 和 MEDV 的小提琴图,内部设置为 ‘point’ 参数
为了测试目的,制作这样的图表是很好的,以便看到底层数据如何连接到视觉。例如,我们可以看到对于相对新部分,没有中位数低于大约 $16,000 的房屋价值数据,因此分布尾部实际上不包含数据。由于我们数据集很小(只有约 500 行),我们可以看到每个段落都是这样的情况。
-
重新构建先前的 pairplot,但现在包括每个
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});
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_54.jpg
图 1.47:使用 AGE 的颜色标签重新构建所有变量的 pairplot
从直方图来看,RM和TAX每个区段的基础分布相似。另一方面,LSTAT的分布看起来更加不同。我们可以通过再次使用小提琴图,进一步关注这些差异。
-
重新构建一个小提琴图,比较每个
AGE_category
区段的 LSTAT 分布:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_01_55.jpg
图 1.48:重新构建的小提琴图,用于比较 AGE_category 区段的 LSTAT 分布
与MEDV小提琴图不同,后者每个分布大致相同宽度,在这里我们看到宽度随着AGE的增大而增加。以老旧房屋为主的社区(Very Old区段)包含的低阶层居民从很少到很多不等,而Relatively New社区则更有可能以高阶层为主,超过 95%的样本低阶层比例低于Very Old社区。这是合理的,因为Relatively New社区的房价较贵。
总结
在本章中,你已经了解了 Jupyter 中数据分析的基本概念。我们从 Jupyter 的使用说明和功能开始,比如魔法函数和自动补全。然后,转到数据科学相关的内容,我们介绍了 Python 数据科学中最重要的库。
在本章的后半部分,我们在实时 Jupyter Notebook 中进行了探索性分析。在这里,我们使用了散点图、直方图和小提琴图等可视化工具,加深了对数据的理解。我们还进行了简单的预测建模,这也是本书下一章的重点内容。
在下一章中,我们将讨论如何进行预测分析,准备数据建模时需要考虑的事项,以及如何在 Jupyter Notebooks 中实现和比较各种模型。
2
第二章:数据清洗与高级机器学习
学习目标
在本章结束时,你将能够:
-
规划机器学习分类策略
-
预处理数据以为机器学习做好准备
-
训练分类模型
-
使用验证曲线来调整模型参数
-
使用降维技术来提升模型性能
在本章中,你将通过完成几个实际练习来学习数据预处理和机器学习。
介绍
假设有一家小型外卖企业,正在寻求优化他们的产品。分析师可能会查看相关数据,并确定人们最喜欢的食物类型。也许他们发现有大量人正在订购最辣的食物选项,表明企业可能失去了那些想要更辣食物的顾客。这是相当基础的,或者有些人可能会说,这是“普通”的分析。
在一个单独的任务中,分析师可以通过对订单量随时间变化进行建模,使用预测分析。通过足够的数据,他们可以预测未来的订单量,从而指导餐厅每天所需的员工数量。这个模型可以考虑天气等因素,以做出最佳预测。例如,一场暴雨可能是增加外卖人员的指示,以弥补交通延误的时间。通过历史天气数据,这类信号可以被编码到模型中。这个预测模型可以节省企业手动考虑这些问题的时间,并通过让顾客满意来节省资金,从而提高客户留存率。
数据分析的总体目标是揭示可操作的洞察,以实现积极的商业结果。在预测分析的情况下,目标是通过确定目标的最可能未来结果,基于先前的趋势和模式,来实现这一点。
预测分析的好处并不仅限于大型科技公司。任何企业只要有合适的数据,都可以找到受益于机器学习的方法。
世界各地的公司正在收集大量数据,并使用预测分析来降低成本并提高利润。最常见的例子来自科技巨头谷歌、Facebook 和亚马逊,他们在大规模使用大数据。例如,谷歌和 Facebook 根据预测算法向你展示个性化广告,这些算法猜测你最有可能点击的内容。同样,亚马逊根据你之前的购买推荐你最有可能购买的个性化产品。
现代预测分析是通过机器学习完成的,其中计算机模型被训练以从数据中学习模式。正如我们在上一章简要看到的那样,像 scikit-learn 这样的软件可以与 Jupyter Notebooks 一起使用,以高效地构建和测试机器学习模型。正如我们将继续看到的,Jupyter Notebooks 是进行这类工作的理想环境,因为我们可以进行临时测试和分析,并且可以轻松保存结果以供以后参考。
在本章中,我们将再次采取动手实践的方式,通过在 Jupyter Notebook 中运行各种示例和活动来进行学习。在上一章中我们看到了几个机器学习的例子,在这里我们将采取更慢、更深思熟虑的方式。以员工留存问题作为本章的总体示例,我们将讨论如何进行预测分析,在为建模准备数据时需要考虑哪些因素,以及如何使用 Jupyter Notebooks 实现和比较多种模型。
准备训练预测模型
在这里,我们将讨论训练预测模型所需的准备工作。虽然这一步骤不像训练模型本身那样在技术上引人注目,但它不应被轻视。在开始构建和训练一个可靠的模型之前,确保你有一个良好的计划是非常重要的。此外,一旦你决定了正确的计划,在为建模准备数据时,还有一些技术步骤不能被忽视。
注意
我们必须小心,不要过于深入技术任务的细节,以至于忽视了最终目标。技术任务包括需要编程技能的内容,例如构建可视化图表、查询数据库和验证预测模型。很容易花费数小时尝试实现特定功能,或者让图表看起来恰到好处。做这种事情确实有助于提高我们的编程技能,但我们不应该忘记问自己,考虑到当前的项目,是否值得花费这么多时间。
此外,请记住,Jupyter Notebooks 特别适合这个步骤,因为我们可以用它们来记录我们的计划,例如,写下关于数据的粗略笔记或我们有兴趣训练的模型列表。在开始训练模型之前,最好进一步走一步,写出一个结构良好的计划来遵循。这样不仅有助于你在构建和测试模型时保持正轨,还能让别人看到你的工作时了解你在做什么。
在讨论完准备工作后,我们还将介绍准备训练预测模型的另一步骤——清理数据集。这是 Jupyter Notebooks 非常适合的一项工作,因为它们提供了一个理想的测试环境,可以执行数据集转换并追踪精确的变化。清理原始数据所需的数据转换可能会迅速变得复杂而难以理解,因此,跟踪你的工作非常重要。如第一章所讨论的,其他工具则不提供非常好的高效操作选项。
在进入下一部分之前,我们先暂停一下,思考一下这些想法,并将其放到现实生活中的示例中来考虑。
考虑以下情况:
你被一家在线视频游戏平台聘用,目的是提高访问他们网站的用户的转化率。他们要求你利用预测分析来判断用户喜欢哪种类型的游戏,以便展示能够促使用户购买的专门化内容。并且他们希望做到这一点,而无需询问客户的游戏偏好。
这是一个可以解决的问题吗?需要什么类型的数据?这对业务会有什么影响?
为了解决这个挑战,我们可以考虑基于用户的浏览器 Cookie 来进行预测。例如,如果他们曾经访问过一个《魔兽世界》网站,那么这个 Cookie 可以作为他们喜欢角色扮演游戏的一个指示器。
另一项有价值的数据是用户在平台上购买过的游戏历史。例如,这可以作为机器学习算法中的目标变量,例如一个能够预测用户可能感兴趣的游戏的模型,这个预测模型可以基于他们浏览会话中的 Cookie 类型。另一种可替代的目标变量可以通过在平台上设置一个调查来收集用户的偏好数据。
在业务影响方面,能够准确预测游戏类型对营销活动的成功至关重要。实际上,预测错误的问题是双重的:不仅错失了瞄准用户的机会,而且还可能向用户展示负面印象的内容。这可能导致更多人离开网站并减少销售。
确定预测分析的计划
在制定预测建模计划时,应该首先考虑利益相关者的需求。如果模型无法解决相关问题,那么它将是无用的。围绕业务需求制定策略可以确保成功的模型带来可行的洞察力。
尽管原则上可以解决许多业务问题,但解决方案的实现总是取决于是否能获得必要的数据。因此,在考虑业务需求时,必须结合可用的数据源。如果数据丰富,这几乎不会有影响,但随着可用数据量的减少,能够解决的问题范围也会缩小。
这些想法可以形成一个标准的过程,用于确定预测分析计划,步骤如下:
-
查看可用数据,了解现实中可解决的业务问题的范围。在这个阶段,可能还太早去考虑能够解决的具体问题。确保你理解可用的数据字段及其适用的时间范围。
-
通过与关键利益相关者交谈,确定业务需求。寻找一个解决方案能够带来可操作的商业决策的问题。
-
评估数据的适用性,通过考虑特征空间的多样性和规模是否足够大。同时,考虑数据的状态:某些变量或时间范围是否存在大量缺失值?
第 2 步和第 3 步应重复进行,直到形成一个现实的计划。此时,你应该已经对模型输入和期望的输出有了清晰的了解。
一旦你确定了可以用机器学习解决的问题,并且找到了合适的数据源,我们应该回答以下问题,以为项目奠定框架。这样做有助于我们确定可以用来解决问题的机器学习模型类型。以下图像提供了根据数据类型可用选择的概述:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_01.jpg
图 2.1:根据数据类型的机器学习策略流程图
上图描述了根据数据类型(有标签或无标签)你可以选择的路径。
如上所示,可以选择有监督学习或无监督学习。有监督学习包括分类或回归问题。在回归中,变量是连续的;例如,降水量。在回归中,变量是离散的,我们预测类别标签。最简单的分类问题是二分类问题;例如,今天会下雨吗?(是/否)
对于无监督学习,聚类分析是常用的方法。此处,标签会分配给每个样本的最近聚类。
然而,决定机器学习策略时,除了数据类型外,数据源的大小和来源也是一个重要因素。具体而言,以下几点应注意:
-
在应用机器学习算法之前,应考虑数据的规模,特别是数据的宽度(列数)和高度(行数)。
-
某些算法在处理某些特征时优于其他算法。
-
一般来说,数据集越大,准确性越高。然而,这也可能会非常耗时。
-
使用降维技术可以节省时间。
-
对于多个数据源,可以考虑将它们合并到一个表格中。
-
如果无法做到这一点,我们可以为每个模型分别训练,并考虑最终预测时使用这些模型的平均结果。
一个我们可能想要这样做的例子是,对于不同尺度的多组时间序列数据。假设我们有以下数据源:一张包含 AAPL 股票每日收盘价的表格,以及一张包含 iPhone 每月销量的表格。我们可以通过将每月的销量数据添加到每日时间尺度表格的每个样本中来合并数据,或者按月对每日数据进行分组,但更好的做法可能是为每个数据集构建两个模型,并将它们的结果结合起来用于最终的预测模型。
数据预处理对机器学习有巨大影响。就像俗话说的“你就是你吃的”,模型的表现直接反映了它所训练的数据。许多模型依赖于数据的转换,以便连续特征值具有可比的范围。同样,类别特征应当被编码为数值类型。尽管这些步骤重要,但它们相对简单且不会花费太长时间。
通常,预处理过程中花费最多时间的环节是清理杂乱的数据。有些估计表明,数据科学家大约将三分之二的工作时间花在清理和整理数据集上:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_02.jpg
图 2.2:不同数据任务花费时间的饼图分布。
要了解更多关于预处理阶段的信息,请参考:www.forbes.com/sites/gilpress/2016/03/23/data-
preparation-most-time-consuming-least-enjoyable-data- science-task-survey-says/2/#17c66c7e1492.
另一个需要考虑的因素是许多数据科学家使用的数据集的大小。随着数据集大小的增加,数据杂乱无章的情况也会增加,清理这些数据的难度也随之增加。
单纯丢弃缺失数据通常不是最佳选择,因为很难合理化丢弃那些大部分字段都有值的样本。在这样做时,我们可能会失去一些宝贵的信息,这可能会影响最终模型的表现。
注意
在这个练习中,我们通过创建两个 DataFrame,执行内连接和外连接,并去除空值(NaN
)来实践数据预处理。
数据预处理的步骤可以分为以下几类:
-
合并数据集,依据公共字段将所有数据整合到一个表格中。
-
特征工程用于提高数据质量,例如,使用降维技术构建新特征。
-
清理数据,通过处理重复行、错误或缺失的值以及其他可能出现的问题
-
构建训练数据集,通过标准化或归一化所需的数据,并将其划分为训练集和测试集
让我们探索一些进行数据预处理的工具和方法。
练习 8:探索数据预处理工具和方法
-
在项目目录下通过执行
jupyter notebook
启动NotebookApp
。进入Lesson-2
目录并打开lesson-2-workbook.ipynb
文件。找到加载包的单元并运行它。我们将从展示 Pandas 和 Sci-kit Learn 的一些基本工具开始。接着,我们将深入探讨重建缺失数据的方法。
-
向下滚动到
子主题 B:为机器学习准备数据
,并运行包含pd.merge?
的单元,以在笔记本中显示合并函数的文档字符串:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_03.jpg图 2.3:合并函数的文档字符串
如我们所见,函数接受左右两个 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
列。 -
运行下一个单元来执行内连接合并:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_04.jpg
图 2.4:列的内连接合并
注意,只有共享的项目,红色衬衫和白色连衣裙,被包括在内。为了包括两个表中的所有条目,我们可以改为进行外连接合并。现在就来做这个。
-
运行下一个单元来执行外连接合并:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_05.jpg
图 2.5:列的外连接合并
这将返回每个表中的所有数据,缺失值会被标记为
NaN
。https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_06.jpg
图 2.6:使用 NumPy 测试质量的代码
你可能已经注意到我们最近合并的表格在前几行有重复的数据。这个问题将在下一步解决。
-
运行包含
df.drop_duplicates()
的单元,返回一个没有重复行的 DataFrame 版本:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_07.jpg图 2.7:删除重复行后的表格
这是删除重复行的最简单和“标准”方法。为了将这些更改应用到
df
,我们可以设置inplace=True
,或者像df = df.drop_duplicated()
这样操作。接下来,我们来看另一种方法,它使用掩码来选择或删除重复行。 -
运行包含
df.duplicated()
的单元格以打印 True/False 序列,标记重复行:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_08.jpg图 2.8:打印重复行的 True/False 值
-
将结果相加,以确定通过运行以下代码检查重复行的数量:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_09.jpg
图 2.9:汇总结果以检查重复行的数量
-
运行以下代码并确认输出与
df.drop_duplicates()
的输出相同:df[~df.duplicated()]
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_10.jpg
图 2.10:来自 df.[~df.duplicated()] 函数的输出
-
运行包含以下代码的单元格,从完整的 DataFrame 中删除重复项:
df[~df['product'].duplicated()]
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_11.jpg
图 2.11:删除重复项后的输出
在这里,我们做了以下操作:
为产品行创建掩码(一个 True/False 序列),其中重复项标记为
True
;使用波浪线(~)来取反该掩码,以便将重复项标记为 False,其他所有内容标记为
True
;使用该掩码来筛选出
False
行,即对应于重复产品的df
行。正如预期的那样,我们现在看到只有第一个
df
,它是去重后的版本。这可以通过运行drop_duplicates
并传递参数inplace=True
来实现。 -
对 DataFrame 进行去重并保存结果,方法是运行包含以下代码的单元格:
df.drop_duplicates(inplace=True)
继续其他预处理方法,我们先忽略重复行,处理缺失数据。这是必要的,因为模型不能在不完整的样本上进行训练。以蓝色裤子和白色晚礼服的缺失价格数据为例,展示几种处理
NaN
值的不同方法。 -
删除行,尤其是在
NaN
样本缺失数据的情况下,通过运行包含df.dropna()
的单元格:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_12.jpg图 2.12:删除不完整行后的输出
-
删除具有大部分缺失值的整个列。通过运行包含与之前相同的方法的单元格,但这次传递
axes
参数以指示列而不是行来实现:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_13.jpg图 2.13:删除缺失值特征列后的输出
简单地删除
NaN
值通常不是最佳选择,因为丢失数据从来不是好事,特别是当只有少部分样本值丢失时。Pandas 提供了一种方法,可以通过多种不同方式填充NaN
条目,下面将展示其中的一些方法。 -
运行包含
df.fillna?
的单元格,打印 PandasNaN-fill
方法的文档字符串:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_14.jpg图 2.14:NaN 填充方法的文档字符串
注意值参数的选项;这可以是例如单个值或基于索引的字典/系列类型映射。或者,我们可以将值保留为
None
,并传递一个fill
方法代替。在本章中我们将看到每种方法的示例。 -
通过运行包含以下代码的单元格,用平均产品价格填充缺失数据:
df.fillna(value=df.price.mean())
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_15.jpg
图 2.15:使用平均产品价格填充缺失数据后的输出
-
通过运行包含以下代码的单元格,使用填充方法填充缺失数据:
df.fillna(method='pad')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_16.jpg
图 2.16:使用填充方法填充数据后的输出
注意白色裙子的价格被用来填充下面缺失的值。
为了完成本练习,我们将准备一个简单的表格,用于训练机器学习算法。别担心,我们不会在这么小的数据集上训练模型!我们从为类别数据编码类标签开始这一过程。
-
运行
Building training data sets
部分中的第一个单元格,添加另一列数据,表示编码标签之前的平均产品评分:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_17.jpg图 2.17:添加评分列后的输出
考虑到我们想用这个表格来训练预测模型,我们首先应该考虑将所有变量转换为数值类型。
-
将
in_stock
(一个布尔值列表)转换为数字值;例如,0
和1
。在用它训练预测模型之前,应该进行此转换。这可以通过许多方法实现,例如,运行包含以下代码的单元格:df.in_stock = df.in_stock.map({False: 0, True: 1})
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_18.jpg
图 2.18:将 in_stock 转换为二进制后的输出
-
运行包含以下代码的单元格,在更高层次上将类标签映射为整数。我们使用 sci-kit learn 的
LabelEncoder
来实现这一目的:from sklearn.preprocessing import LabelEncoder rating_encoder = LabelEncoder() _df = df.copy() _df.rating = rating_encoder.fit_transform(df.rating) _df
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_19_.jpg
图 2.19:将类标签映射到整数后的输出
这可能会让你想起我们在上一章中做的预处理,当时我们构建了多项式模型。在这里,我们实例化了一个标签编码器,然后使用
fit_transform
方法对其进行“训练”和“转换”数据。我们将结果应用到我们的 DataFrame 副本_df
上。 -
通过运行
rating_encoder.inverse_transform(df.rating)
,使用我们通过变量rating_encoder
引用的类重新转换特征:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_20.jpg图 2.20:执行逆变换后的输出
你可能会注意到一个问题。我们正在处理一个所谓的“顺序”特征,其中标签有固有的顺序。在这种情况下,我们应该期望“low”评级会被编码为 0,而“high”评级会被编码为 2。然而,这不是我们看到的结果。为了实现正确的顺序标签编码,我们应该再次使用
map
,并自己构建字典。 -
通过运行包含以下代码的单元格,正确地对顺序标签进行编码:
ordinal_map = {rating: index for index, rating in enumerate(['low', 'medium', 'high'])} print(ordinal_map) df.rating = df.rating.map(ordinal_map)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_21.jpg
图 2.21:编码顺序标签后的输出
我们首先创建映射字典。这是通过字典推导和枚举来完成的,但从结果来看,我们也可以直接手动定义。然后,像之前对
in_stock
列进行的操作一样,我们将字典映射应用到特征上。查看结果后,我们发现评分现在比之前更合理,其中low
被标记为0
,medium
为1
,high
为2
。现在,你已经讨论了顺序特征,接下来我们将简要提及另一种特征,称为名义特征。这些字段没有固有的顺序,在我们的案例中,
product
就是一个完美的例子。大多数 scikit-learn 模型可以在这种数据上进行训练,其中我们使用字符串而非整数编码的标签。在这种情况下,必要的转换会在后台自动完成。然而,这并不适用于所有 scikit-learn 模型,或其他机器学习和深度学习库。因此,在预处理阶段我们自己进行编码是一个好的做法。
-
通过运行包含以下代码的单元格,将类别标签从字符串转换为数值:
df = pd.get_dummies(df)
最终的 DataFrame 如下所示:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_201.jpg
图 2.22:最终的 DataFrame
这里,我们看到的是独热编码的结果:
product
列被拆分为 4 列,每个列代表一个唯一的值。在每一列中,我们看到1
或0
,表示该行是否包含特定值或产品。接下来,忽略任何数据缩放(通常应该进行缩放),最后一步是将数据拆分为训练集和测试集,以便用于机器学习。这可以通过 scikit-learn 的
train_test_split
来完成。假设我们要尝试预测某个商品是否有库存,前提是其他特征值已知。注意
当我们调用前面代码中的
values
属性时,我们将 Pandas 系列(即 DataFrame 列)转换为 NumPy 数组。这是一个好的做法,因为它去除了系列对象中不必要的信息,例如索引和名称。 -
通过运行包含以下代码的单元格,将数据拆分为训练集和测试集:
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)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_23.jpg
图 2.23:将数据划分为训练集和测试集
在这里,我们选择数据的子集,并将它们传入train_test_split
函数。该函数有四个输出,这些输出被拆分成特征(X
)和目标(y
)的训练集和测试集。
观察输出数据的形状,其中测试集大约占样本的 30%,训练集大约占 70%。
稍后我们会看到类似的代码块,用于准备真实数据以进行预测模型的训练。
本次训练练习已结束,主题是清理数据以供机器学习应用使用。我们稍作停留,来注意一下我们的 Jupyter Notebook 在测试各种数据转换方法时的有效性,并最终记录我们决定使用的数据处理流程。通过仅修改特定的代码单元格,就可以轻松应用到更新版的数据,在处理之前进行调整。此外,如果我们希望对数据处理做出任何更改,也可以轻松地在 Notebook 中测试这些更改,并可以修改特定单元格以适应调整。实现这一点的最好方法可能是将 Notebook 复制到新文件中,以便我们始终保留原始分析的副本作为参考。
接下来,我们将应用本节中的概念,将它们应用于一个大型数据集,为训练预测模型做准备。
活动 2:准备为员工留任问题训练预测模型
假设你受雇为一家公司做自由职业工作,帮助他们分析员工离职的原因。他们已经收集了一些数据,认为这些数据对分析离职原因有帮助。这些数据包括员工的满意度、评估、工作时间、部门和薪资等信息。
该公司通过发送名为 hr_data.csv 的文件与你共享他们的数据,并请你提出解决方案,以帮助阻止员工离职。
我们的目标是
将你到目前为止学到的概念应用到实际问题中。特别是,我们希望:
-
制定一个计划,利用预测分析为业务提供有影响力的洞察,基于现有的数据。
-
准备数据以供机器学习模型使用。
注意
从本活动开始,直到本章结束,我们将使用人力资源分析数据集,这是一个 Kaggle 数据集。数据集的链接可以在这里找到:
bit.ly/2OXWFUs
。这些数据是模拟数据,意味着样本是人工生成的,并不代表真实的人。我们在分析和建模数据时将忽略这一点。我们使用的数据集与在线版本存在一些小差异。我们的 HR 分析数据集包含一些 NaN 值。这些值已经在在线版本中手动删除,以说明数据清理技术。我们还为同样的目的,添加了一列名为 is_smoker 的数据。
为了实现这一目标,需要执行以下步骤:
-
滚动到
lesson-2-workbook.ipynb
笔记本文件中的Activity A
部分。 -
检查表格的头部,确认它是标准的 CSV 格式。
-
使用 Pandas 加载数据。
-
通过打印
df.columns
检查列,并通过打印 DataFrame 的head
和tail
(使用df.head()
和df.tail()
)来确保数据已按预期加载: -
检查 CSV 文件中的行数(包括标题)。
-
将此结果与
len(df)
进行比较,确保我们已经加载了所有数据: -
评估目标变量并检查缺失值的分布和数量。
-
打印每个特征的数据类型。
-
显示特征分布。
-
通过运行以下代码检查每一列中的
NaN
值数量: -
删除
is_smoker
列,因为该指标几乎没有任何信息。 -
填充
time_spend_company
列中的NaN
值。 -
绘制
average_montly_hours
按number_project
分段的箱型图。 -
通过运行以下代码计算每组的均值:
-
填充
average_montly_hours
中的NaN
值。 -
通过运行断言测试,确认
df
中不再有NaN
值。 -
将字符串和布尔字段转换为整数表示。
-
打印
df.columns
以显示字段 -
保存我们预处理后的数据。
注意
详细步骤和解决方案在附录 A(第 150 页)中列出。
再次,我们在这里暂停,注意到在执行初步数据分析和清理时,Jupyter Notebook 非常适合我们的需求。例如,假设我们将这个项目暂时搁置几个月。返回时,我们可能不记得当时到底发生了什么。但如果查看这个笔记本,我们可以重新追溯我们的步骤,并快速回忆起我们之前对数据的理解。此外,我们可以用任何新数据更新数据源,并重新运行笔记本,为机器学习算法准备新的数据集。请记住,在这种情况下,最好先复制一份笔记本,以免丢失初始分析。
总结一下,你已经学习并应用了为训练机器学习模型做准备的方法。我们从讨论识别可以通过预测分析解决的问题的步骤开始。这个过程包括:
-
查看可用数据
-
确定业务需求
-
评估数据的适用性
我们还讨论了如何区分监督学习与无监督学习,以及回归问题与分类问题。
在确定问题后,我们学习了使用 Jupyter Notebooks 构建和测试数据转换管道的技术。这些技术包括填充缺失数据、转换分类特征以及构建训练/测试数据集的方法和最佳实践。
在本章的剩余部分,我们将使用这些预处理后的数据来训练各种分类模型。为了避免盲目应用我们不了解的算法,我们首先介绍它们并概述它们的工作原理。然后,我们使用 Jupyter 训练并比较它们的预测能力。在这里,我们有机会讨论机器学习中的更高级话题,比如过拟合、k 折交叉验证和验证曲线。
训练分类模型
正如你在上一章中已经看到的,通过使用像 scikit-learn 这样的库和像 Jupyter 这样的平台,可以用几行代码训练预测模型。这之所以可能,是因为它将优化模型参数所涉及的复杂计算进行了抽象。换句话说,我们处理的是一个“黑箱”,其内部操作是隐藏的。然而,这种简化也带来了滥用算法的风险,例如,在训练过程中出现过拟合,或者未能在未见数据上进行适当的测试。我们将展示如何在训练分类模型时避免这些陷阱,并通过使用 k 折交叉验证和验证曲线来生成可靠的结果。
分类算法简介
回顾一下监督学习的两种类型:回归和分类。在回归中,我们预测一个连续的目标变量。例如,回想一下第一章中的线性和多项式模型。在本章中,我们关注的是另一种监督学习类型:分类。这里的目标是使用可用的度量来预测样本的类别。
在最简单的情况下,只有两个可能的类别,这意味着我们正在进行二分类。我们在本章的示例问题中就是这种情况,我们尝试预测一个员工是否已经离职。如果我们有两个以上的类别标签,那么我们就是在进行多分类。
尽管在使用 scikit-learn 训练模型时,二分类和多分类之间几乎没有区别,但在“黑箱”内部所做的工作却有显著不同。特别是,多分类模型通常使用一对多的方法。这对于具有三个类别标签的情况是这样工作的。当模型用数据进行“拟合”时,会训练三个模型,每个模型预测样本是否属于某个特定类别或属于其他类别。这可能让你想起我们之前为特征做的一次性编码。当为某个样本做出预测时,将返回具有最高置信度的类别标签。
在本章中,我们将训练三种分类模型:支持向量机、随机森林和 k 最近邻分类器。这些算法各自有很大的不同。然而,正如我们将看到的,由于 scikit-learn,它们在训练和用于预测时非常相似。在切换到 Jupyter Notebook 并实现这些模型之前,我们将简要了解它们是如何工作的。
支持向量机(SVM)尝试找到最佳的超平面来划分类别。这是通过最大化超平面与每个类别中最接近样本之间的距离来实现的,这些最接近的样本被称为支持向量。
这种线性方法也可以通过核技巧用于建模非线性类别。该方法将特征映射到更高维的空间中,在其中确定超平面。这个超平面也被称为决策面,我们将在训练模型时将其可视化。
K 最近邻分类算法记住训练数据,并根据特征空间中 K 个最近的样本进行预测。对于三个特征,这可以被可视化为围绕预测样本的一个球体。然而,通常我们处理的是超过三个特征的数据,因此会绘制超球体来找到最近的 K 个样本。
随机森林是一组决策树,每棵树都在不同的训练数据子集上进行训练。
决策树算法根据一系列决策对样本进行分类。例如,第一个决策可能是“如果特征 x_1 小于或大于 0”。然后,数据会根据这个条件进行划分,并被输入到树的下级分支中。决策树中的每一步都是基于特征划分来决定的,这一划分最大化信息增益。实际上,这个术语描述了试图选择目标变量最佳划分的数学方法。
训练随机森林的过程包括为一组决策树创建自助采样(即带有替换的随机采样)数据集。然后,根据多数投票做出预测。这些模型的优势在于减少了过拟合,并且具有更好的泛化能力。
注意
决策树可以用于建模连续数据和类别数据的混合,这使得它们非常有用。此外,正如我们将在本章稍后看到的,可以限制树的深度以减少过拟合。要详细(但简洁)了解决策树算法,请查看这个流行的 StackOverflow 回答:stackoverflow.com/a/1859910/3511819
。在那里,作者展示了一个简单的示例,并讨论了节点纯度、信息增益和熵等概念。
练习 9:使用 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')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_24.jpg
图 2.24:满意度和最后评估的双变量与单变量图
正如你在前面的图像中看到的,数据中有一些非常明显的模式。
-
通过运行包含以下代码的单元,重新绘制按目标变量分段的双变量分布:
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)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_25.jpg
图 2.25:满意度和最后评估的双变量分布
现在,我们可以看到这些模式如何与目标变量相关。在本次练习的剩余部分,我们将尝试利用这些模式来训练有效的分类模型。
-
通过运行包含以下代码的单元,将数据分割为训练集和测试集:
from sklearn.model_selection import train_test_split features = ['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
并创建一个新的实例,如缩放器变量所示。对训练集进行拟合并转换,然后转换测试集。运行包含以下代码的单元:from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_std = scaler.fit_transform(X_train) X_test_std = scaler.transform(X_test)
注意
在进行机器学习时,一个常见的错误是将缩放器(scaler)“拟合”到整个数据集,而实际上它应该只对训练数据进行“拟合”。例如,在将数据分割为训练集和测试集之前进行缩放就是一个错误。我们不希望这样做,因为模型训练不应该受到测试数据的任何影响。
-
导入 scikit-learn 的支持向量机类,并通过运行包含以下代码的单元,在训练数据上拟合模型:
from sklearn.svm import SVC svm = SVC(kernel='linear', C=1, random_state=1) svm.fit(X_train_std, y_train)
-
通过运行包含以下代码的单元,计算该模型在未见数据上的准确度:
from sklearn.metrics import accuracy_score y_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_matrix cmat = confusion_matrix(y_test, y_pred) scores = cmat.diagonal() / cmat.sum(axis=1) * 100 print('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_regions N_samples = 200 X, y = X_train_std[:N_samples], y_train[:N_samples] plot_decision_regions(X, y, clf=svm)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_26.jpg
图 2.26: 决策区域的绘图
该函数绘制决策区域,并与作为参数传递的一组样本一起显示。为了正确显示决策区域,避免太多样本阻挡视线,我们只将测试数据的 200 个样本子集传递给
plot_decision_regions
函数。当然,在这种情况下,样本数量并不重要。我们看到结果完全是红色,表示特征空间中的每个点都会被分类为 0。线性模型无法很好地描述这些非线性模式,这并不令人惊讶。回想一下,我们之前提到过通过 SVM 使用核技巧来分类非线性问题。让我们看看这样做是否能改善结果。
-
运行包含 SVC 的单元以打印 scikit-learn 的文档字符串。向下滚动并查看参数描述。注意内核选项,默认情况下实际上是启用
rbf
。使用这个kernel
选项训练一个新的 SVM,运行包含以下代码的单元:check_model_fit(svm, X_test_std, y_test)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_27.jpg
图 2.27: 训练一个新的 SVM
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_28.jpg
图 2.28: 使用非线性模式增强的结果
结果明显更好。现在,我们能够捕捉到数据中的一些非线性模式,并正确分类大多数已经离职的员工。
plot_decision_regions 函数
plot_decision_regions
函数由 mlxtend
提供,这是由 Sebastian Raschka 开发的一个 Python 库。值得看看源代码(当然是用 Python 编写的),以理解这些图是如何绘制的。其实并不复杂。
在 Jupyter Notebook 中,使用 from mlxtend.plotting import plot_decision_regions
导入函数,然后通过 plot_decision_regions?
调出帮助并滚动到底部查看本地文件路径:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_29.jpg
图 2.29: 本地文件路径
然后,打开文件并阅读它。例如,您可以在笔记本中运行 cat
命令:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_30.jpg
图 2.30:在笔记本中运行 cat 命令
这虽然可以,但不理想,因为代码没有颜色标记。最好复制它(这样就不会意外修改原始代码),然后用你喜欢的文本编辑器打开它。
当我们将注意力集中在负责映射决策区域的代码时,我们可以看到一个关于预测 Z 的等高线图,这些预测覆盖了特征空间的数组X_predict
。
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_31.jpg
](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_31.jpg)
图 2.31:映射决策区域的代码截图
让我们开始训练我们的 k-近邻模型。
练习 10:为我们的模型训练 K-近邻分类器
-
加载 scikit-learn KNN 分类模型,并通过运行包含以下代码的单元格打印文档字符串:
from sklearn.neighbors import KNeighborsClassifier KNeighborsClassifier?
n_neighbors
参数决定了分类时使用多少个样本。如果权重参数设置为 uniform,则类标签由多数投票决定。权重的另一个有用选择是距离,在这种情况下,离得较近的样本在投票中的权重较高。像大多数模型参数一样,最佳选择取决于特定的数据集。 -
使用
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)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_32.jpg
图 2.32:使用 n_neighbors=3 训练 kNN 分类器
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_33.jpg
图 2.33:训练后增强的结果
我们看到整体准确率有所提高,尤其是类 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)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_34.jpg
图 2.34:使用 n_neighbors=25 训练 kNN 分类器
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_35.jpg
图 2.35:使用 n_neighbors=25 训练后的输出
正如我们所看到的,决策边界明显平滑了很多,蓝色区域也少了很多。类 1 的准确率略低,但我们需要使用更全面的方法,比如 k 折交叉验证,来决定这两个模型之间是否存在显著差异。
请注意,增加 n_neighbors
对训练时间没有影响,因为模型只是记忆数据。然而,预测时间将受到极大影响。
注意
在使用真实世界数据进行机器学习时,算法的运行速度足够快以满足需求非常重要。例如,一个预测明天天气的脚本如果运行超过一天才完成,那完全没有意义!在处理大量数据时,内存也是需要考虑的一个因素。
我们现在将训练一个随机森林。
练习 11:训练随机森林
注意
观察尽管每个模型内部差异巨大,但它们在训练和预测时是如此相似。
-
训练一个由 50 棵决策树组成的随机森林分类模型,每棵树的最大深度为 5。运行包含以下代码的单元:
from sklearn.ensemble import RandomForestClassifier forest = RandomForestClassifier(n_estimators=50, max_depth=5, random_state=1) forest.fit(X_train, y_train) check_model_fit(forest, X_test, y_test)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_36.jpg
图 2.36:训练最大深度为 5 的随机森林
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_37.jpg
图 2.37:训练最大深度为 5 后的输出
注意决策树机器学习算法所产生的独特的与轴平行的决策边界。
我们可以访问用于构建随机森林的任意单棵决策树。这些树存储在模型的
estimators_attribute
中。让我们画出其中一棵决策树,以便了解发生了什么。这样做需要graphviz依赖项,而它有时安装起来比较麻烦。 -
通过运行包含以下代码的单元,绘制 Jupyter Notebook 中的一棵决策树:
from sklearn.tree import export_graphviz import graphviz dot_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
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_38.jpg
图 2.38:使用 graphviz 获得的决策树
我们可以看到,由于设置了 max_depth=5,每条路径被限制为五个节点。橙色框代表“否”(员工未离开公司)的预测,蓝色框代表“是”(员工已离开公司)的预测。每个框的阴影(浅色、深色等)表示置信度水平,这与基尼值相关。
总结来说,我们已经完成了本节中的两个学习目标:
-
我们获得了支持向量机(SVM)、k 最近邻分类器(kNN)和随机森林的定性理解。
-
我们现在能够使用 scikit-learn 和 Jupyter Notebooks 训练各种模型,从而可以自信地构建和比较预测模型。
特别是,我们使用了来自员工流失问题的预处理数据,训练了分类模型来预测员工是否已离开公司。为了简化问题并专注于算法,我们构建了只利用两个特征:满意度水平和最后评估值,来预测这一问题的模型。这个二维特征空间还使我们能够可视化决策边界并识别过拟合的情况。
在接下来的章节中,我们将介绍机器学习中的两个重要主题:k 折交叉验证和验证曲线。
使用 K 折交叉验证和验证曲线评估模型
到目前为止,我们已经在数据的子集上训练了模型,然后评估了未见部分的表现,称为测试集。这是良好的做法,因为模型在训练数据上的表现并不能很好地指示它作为预测器的有效性。通过对模型过拟合,很容易在训练数据集上提高准确性,但这可能导致在未见数据上的表现更差。
也就是说,仅仅按照这种方式划分数据并训练模型是不够的。数据本身有天然的变异性,这导致准确性会有所不同(即使是轻微的),取决于训练和测试的分割。此外,仅使用一个训练/测试分割来比较模型可能会引入偏向某些模型的偏差,并导致过拟合。
K 折交叉验证提供了这个问题的解决方案,并允许通过每次准确性计算的误差估计来考虑方差。反过来,这自然导致了使用验证曲线来调整模型参数。这些曲线将准确性作为超参数的函数进行绘制,例如在随机森林中使用的决策树数量或最大深度。
注意
这是我们第一次使用“超参数”这个术语。它指的是在初始化模型时定义的参数,例如 SVM 的 C 参数。这与训练后模型的参数不同,例如训练后的 SVM 的决策边界超平面的方程。
该方法在以下图示中进行了说明,展示了如何从数据集中选择 k 折:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_39.jpg
图 2.39:从数据集中选择 k 折
K 折交叉验证算法如下:
-
将数据分成 k 个近似相等的“折叠”。
-
在不同的折叠组合上测试和训练 k 个模型。每个模型将包括 k - 1 个折叠的训练数据,剩下的折叠用于测试。在这种方法中,每个折叠最终会被用作验证数据,且仅使用一次。
-
通过取 k 个值的均值来计算模型的准确性。还会计算标准偏差,以提供准确性值的误差条。
通常设置k = 10,但如果使用大数据集,则应考虑使用较小的 k 值。
这种验证方法可以用来可靠地比较不同超参数下的模型表现(例如 SVM 的 C 参数或 KNN 分类器中的最近邻数)。它也适用于比较完全不同的模型。
一旦确定了最佳模型,应在整个数据集上重新训练它,然后再用于预测实际分类。
在使用 scikit-learn 实现时,通常会使用一种稍微改进的标准 k-fold 算法变体。这种变体叫做分层 k-fold。其改进之处在于,分层 k-fold 交叉验证在每个折叠中保持大致均匀的类别标签分布。正如你能想象的,这减少了模型的总体方差,并降低了高度不平衡的模型可能引起偏差的概率。
验证曲线是训练和验证指标作为某些模型参数的函数的图表。它们可以帮助我们做出好的模型参数选择。在本书中,我们将使用准确率得分作为这些图表的指标。
注意
关于绘制验证曲线的文档可在此查阅:scikit-learn.org/stable/auto_examples/
model_selection/plot_validation_curve.html。
请参考此验证曲线,其中准确率分数作为 gamma SVM 参数的函数进行绘制:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_40.jpg
图 2.40:使用支持向量机(SVM)的验证曲线
从图表的左侧开始,我们可以看到两组数据的得分一致,这是好的。然而,与其他 gamma 值相比,得分也相对较低,因此我们可以说模型存在欠拟合现象。当增加 gamma 值时,我们可以看到一个点,在这个点上,这两条线的误差条不再重叠。从这个点开始,我们看到分类器在训练集上的表现逐渐优于验证集,说明模型过拟合数据。可以通过寻找具有重叠误差条的高验证得分来找到 gamma 参数的最优值。
请记住,某些参数的学习曲线仅在其他参数保持不变时有效。例如,在此图中训练 SVM 时,我们可以选择 gamma
的某个值。然而,我们可能还需要优化 C
参数。对于不同的 C
值,前述的图形会有所不同,我们对 gamma
的选择也可能不再最优。
练习 12:在 Python 中使用 K-fold 交叉验证和验证曲线,借助 Scikit-learn
-
启动
NotebookApp
并打开lesson-2-workbook.ipynb
文件。向下滚动至子主题 B:K-fold 交叉验证和验证曲线
部分。训练数据应该已经存在于笔记本的内存中,但我们还是重新加载一次,以便提醒自己到底在使用什么数据。
-
加载数据并选择
satisfaction_level
和last_evaluation
特征作为训练/验证集。由于我们将使用 k-fold 验证,而不是训练-测试拆分,因此这次我们不使用拆分。运行包含以下代码的单元格: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-fold 交叉验证训练模型,我们将使用
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
注意
有关完整代码,请参阅以下链接:
bit.ly/2O5uP3h
。 -
然后,我们可以使用与第 4 步非常相似的代码计算类别准确率。通过运行包含以下代码的单元格来实现:
from sklearn.model_selection import cross_val_score np.random.seed(1) … … >> fold: 10 accuracy: [ 0.98861646 0.70588235] >> accuracy = [ 0.98722476 0.71715647] +/- [ 0.00330026 0.02326823]
注意
有关完整代码,请参阅以下链接:
bit.ly/2EKK7Lp
。 -
现在我们可以看到每个折中的类别准确率了!很棒,对吧?
-
使用
model_selection.validation_curve
计算验证曲线。此函数使用分层 k 折交叉验证来训练模型,并针对给定参数的不同值进行训练。通过在一系列
max_depth
值上训练随机森林来执行绘制验证曲线所需的计算。运行包含以下代码的单元格:from sklearn.model_selection import validation_curve 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')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_41.jpg
图 2.41:绘制验证曲线
回想一下,设置决策树的最大深度是如何限制过拟合的。这在验证曲线中有所体现,在右侧我们可以看到过拟合的现象,尤其是对于较大的最大深度值。一个好的max_depth
值似乎是 6,这时训练准确率和验证准确率一致。当max_depth
为 3 时,模型对数据出现欠拟合,因为训练准确率和验证准确率较低。
总结来说,我们已经学习并实施了两种用于构建可靠预测模型的重要技术。第一种技术是 k 折交叉验证,它用于将数据划分为不同的训练/测试批次并生成一组准确度。从这一组中,我们计算了平均准确度和标准差作为误差度量。这很重要,因为它让我们可以衡量模型的变异性,并生成可信的准确度。
我们还学习了另一种确保结果可信的方法:验证曲线。这些曲线使我们能够通过比较训练和验证准确度来可视化模型何时出现过拟合。通过绘制在我们选定的超参数范围内的曲线,我们能够识别其最佳值。
在本章的最后部分,我们将把到目前为止学到的所有内容整合在一起,以便构建我们的最终员工留存问题预测模型。通过将数据集中的所有特征包含在模型中,我们旨在提高与目前为止训练的模型相比的准确性。我们将看到现在已经熟悉的主题,如 k 折交叉验证和验证曲线,但我们还会引入一些新的内容:降维技术。
降维技术
降维可以简单地通过去除训练数据中不重要的特征来实现,但也存在更为复杂的方法,如主成分分析(PCA)和线性判别分析(LDA)。这些技术允许进行数据压缩,其中来自一组大量特征的最重要信息可以仅通过几个特征来编码。
在这个子主题中,我们将重点讨论 PCA。这项技术通过将数据投影到一个新的正交“主成分”子空间中来转换数据,其中具有最高特征值的成分编码了训练模型所需的最重要信息。然后,我们可以简单地选择这些主成分中的一部分,代替原始的高维数据集。例如,PCA 可以用于编码图像中每个像素的信息。在这种情况下,原始特征空间的维度等于图像中的像素数。然后,通过 PCA 可以减少这个高维空间,其中用于训练预测模型的大部分有用信息可能会被缩减到只有几个维度。这不仅节省了训练和使用模型的时间,还通过去除数据集中的噪声来提高模型的性能。
就像你看到的模型一样,利用 PCA 的好处并不需要深入了解 PCA。然而,我们将稍微探讨一下 PCA 的技术细节,以便更好地理解它。PCA 的关键思想是基于特征间的相关性识别模式,因此,PCA 算法计算协方差矩阵,然后将其分解为特征向量和特征值。接着,这些向量被用来将数据转换到一个新的子空间,从中可以选择固定数量的主成分。
在接下来的练习中,我们将看到一个例子,展示如何使用 PCA 来改善我们在员工流失问题上使用的随机森林模型。这将在使用完整特征空间训练分类模型之后进行,以便观察降维对我们准确率的影响。
练习 13:为员工流失问题训练预测模型
我们已经花费了相当多的精力来规划机器学习策略、预处理数据,并为员工流失问题构建预测模型。回顾一下我们的商业目标是帮助客户防止员工离职。我们决定的策略是构建一个分类模型,预测员工离职的概率。这样,公司就可以评估当前员工离职的可能性,并采取措施加以防止。
根据我们的策略,我们可以总结出我们正在进行的预测建模类型如下:
-
有监督学习(使用带标签的训练数据)
-
二分类问题
特别地,我们正在训练模型,以便根据一组连续和分类特征来判断员工是否已离开公司。在活动 1:为员工流失问题训练预测模型中准备数据后,我们实现了 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
注意
完整代码请参考:
bit.ly/2D3WOQ2
。查看特征名称,回想每个特征的值长什么样。向上滚动查看我们在第一个活动中制作的直方图,以帮助唤起记忆。前两个特征是连续型的;这些是我们在之前的两个练习中用于训练模型的特征。之后,我们有一些离散型特征,例如
number_project
和time_spend_company
,接着是一些二元特征,如work_accident
和promotion_last_5years
。我们还有一些二元特征,例如department_IT
和department_accounting
,这些是通过独热编码创建的。考虑到这样的特征混合,随机森林是一种非常有吸引力的模型类型。首先,它们可以兼容由连续数据和分类数据组成的特征集,但这并不特别独特;例如,支持向量机(SVM)也可以在混合类型的特征上进行训练(只要有适当的预处理)。
注意
如果你有兴趣在混合类型的输入特征上训练支持向量机(SVM)或 k 近邻分类器,可以使用此 StackExchange 回答中的数据缩放建议:
stats.stackexchange.com/questions/82923/mixing-continuous-and-binary-data-with-linear-svm/83086#83086
。一种简单的方法是按如下方式预处理数据:
对连续变量进行标准化;对分类特征进行独热编码;将二元值从 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);
我们正在使用 k 折交叉验证测试 10 个模型。通过设置 k = 5,我们为每个模型生成五个准确率估计值,并从中提取均值和标准差来绘制验证曲线。总共,我们训练了 50 个模型,并且由于
n_estimators
设置为 20,所以我们一共训练了 1,000 棵决策树!这一切大约在 10 秒钟内完成! -
使用我们在上一个练习中创建的自定义
plot_validation_curve
函数绘制验证曲线。运行以下代码:plot_validation_curve(train_scores, test_scores, max_depths, xlabel='max_depth');
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_42.jpg
图 2.42:为不同的 max_depth 值绘制验证曲线
对于较小的最大深度,我们看到模型对数据的拟合不足。通过允许决策树更深,从而能够编码数据中的更复杂模式,整体准确率显著提高。随着最大深度的进一步增加且准确率接近 100%,我们发现模型开始对数据进行过拟合,导致训练和验证准确率出现分歧。根据这个图,我们选择将模型的
max_depth
设为 6。我们本应对
n_estimators
做同样的操作,但为了节省时间,我们跳过了这一步。你可以自行绘制,应该会发现训练集和验证集在较大范围的值上是一致的。通常,在随机森林中使用更多的决策树估计器效果更好,但代价是训练时间增加。我们将使用 200 个估计器来训练模型。 -
使用我们之前创建的按类的 k 折交叉验证函数
cross_val_class_score
来测试所选模型,一个随机森林模型,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')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_43.jpg
图 2.43:使用箱形图可视化准确度
随机森林可以提供特征表现的估算。
注意
在 scikit-learn 中,特征重要性是根据节点不纯度如何随每个特征的变化而计算的。欲了解更详细的解释,请查看以下关于如何在随机森林分类器中确定特征重要性的 StackOverflow 讨论:
stackoverflow.com
-
通过运行以下代码绘制存储在
feature_importances_
属性中的特征重要性:pd.Series(clf.feature_importances_, name='Feature importance', index=df[features].columns)\ .sort_values()\ .plot.barh() plt.xlabel('Feature importance')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_44.jpg
图 2.44:特征重要性的图表
-
看起来来自 one-hot 编码变量(部门和薪水)的贡献不大。此外,
promotion_last_5years
和work_accident
特征似乎也不是很有用。让我们使用 PCA 将这些弱特征压缩为几个主成分。
-
从 scikit-learn 导入
PCA
类并转换特征。运行以下代码:from sklearn.decomposition import PCA pca_features = \ … … pca = PCA(n_components=3) X_pca = pca.fit_transform(X_reduce)
注意
>> 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]])
由于我们请求了前三个主成分,因此返回了三个向量。
-
使用以下代码将新特征添加到我们的数据框中:
df['first_principle_component'] = X_pca.T[0] df['second_principle_component'] = X_pca.T[1] df['third_principle_component'] = X_pca.T[2]
选择我们的降维特征集,使用它来训练一个新的随机森林。运行以下代码:
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 指向不同的特征。代码如下:
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]
-
用与之前相同的方式,通过箱形图可视化结果。代码如下:
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')
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_02_45.jpg
图 2.45:用于可视化准确度的箱形图
将此结果与之前的结果进行比较,我们发现类 1 的准确度有所提高!现在,大部分验证集的准确度超过了 90%。平均准确度为 90.6%,相比于降维前的 85.6% 要高很多!
让我们选择这个作为最终模型。我们需要在完整样本空间上重新训练它,然后再投入生产使用。
-
通过运行以下代码训练最终的预测模型:
np.random.seed(1) clf = RandomForestClassifier(n_estimators=200, max_depth=6) clf.fit(X, y)
-
使用
externals.joblib.dump
将训练好的模型保存到二进制文件。运行以下代码:from sklearn.externals import joblib joblib.dump(clf, 'random-forest-trained.pkl')
-
检查它是否已保存到工作目录中,例如,通过运行:
!ls *.pkl
。然后,运行以下代码测试我们是否可以从文件加载模型:clf = joblib.load('random-forest-trained.pkl')
恭喜!你已经训练好了最终的预测模型!现在,让我们来看一个示例,展示如何利用该模型为客户提供商业洞察。假设我们有一个特定的员工,我们称她为 Sandra。管理层注意到她工作非常努力,并且在最近的调查中报告了低的工作满意度。因此,他们希望了解她离职的可能性有多大。为了简化起见,我们将她的特征值作为训练集的一个样本(但假装这是未见过的数据)。
-
通过运行以下代码列出 Sandra 的特征值:
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 应该属于哪个组。
-
通过运行以下代码预测 Sandra 的类别标签:
clf.predict([X]) >> array([1])
模型将她分类为已经离开公司;这可不是个好兆头!我们可以进一步计算每个类别标签的概率。
-
使用
clf.predict_proba
来预测我们的模型预测 Sandra 已经离职的概率。运行以下代码:clf.predict_proba([X]) >> array([[ 0.06576239, 0.93423761]])
我们看到模型预测她已经离职的准确率为 93%。由于这显然是管理层的一个红旗信号,他们决定采取一个计划,将她的每月工作时间减少到 100 小时,将在公司的时间减少到 1 年。
-
使用 Sandra 新的计划指标计算新的概率。运行以下代码:
X.average_montly_hours = 100 X.time_spend_company = 1 clf.predict_proba([X]) >> array([[ 0.61070329, 0.38929671]])
太棒了!我们现在可以看到,模型预测她已经离职的概率仅为 38%!相反,模型现在预测她不会离开公司。
我们的模型帮助管理层做出了数据驱动的决策。通过将她在公司工作的时间减少到这个特定的数值,模型告诉我们她最有可能继续在公司工作!
总结
在本章中,我们已经看到如何在 Jupyter Notebooks 中训练预测模型。
首先,我们讨论了如何规划机器学习策略。我们考虑了如何设计一个能够提供可操作商业洞察的计划,并强调了使用数据来帮助设定现实商业目标的重要性。我们还解释了机器学习术语,如监督学习、无监督学习、分类和回归。
接下来,我们讨论了使用 scikit-learn 和 pandas 进行数据预处理的方法。这包括对机器学习中一个出人意料且耗时的部分——处理缺失数据——的详细讨论和示例。
在本章的后半部分,我们为二分类问题训练了预测分类模型,并比较了如何为不同模型(如支持向量机、k-最近邻和随机森林)绘制决策边界。然后,我们展示了如何利用验证曲线做出合理的参数选择,并且展示了降维如何提高模型性能。最后,在活动结束时,我们探讨了如何将最终模型应用于实际,做出基于数据的决策。
在下一章,我们将重点讨论数据获取。具体来说,我们将分析 HTTP 请求,从网页抓取表格数据,构建和转换 Pandas DataFrame,最后创建可视化图表。
3
第三章:网页抓取和交互式可视化
学习目标
到本章结束时,你将能够:
-
描述 HTTP 请求的工作原理
-
从网页抓取表格数据
-
构建和转换 Pandas DataFrame
-
创建交互式可视化
在本章中,你将学习 HTTP 请求的基本原理,抓取网页数据,然后使用 Jupyter Notebook 创建交互式可视化。
介绍
到目前为止,在本书中,我们主要集中在使用 Jupyter 构建可重复的数据分析管道和预测模型。本章我们将继续探讨这些话题,但这里的主要重点是数据获取。特别是,我们将展示如何使用 HTTP 请求从互联网上获取数据。这将涉及通过请求和解析 HTML 来抓取网页。接着,我们将通过使用交互式可视化技术来探索我们收集的数据,作为本章的总结。
网上可用的数据量巨大,且相对容易获取。而且,这些数据还在不断增长,变得越来越重要。持续增长的一部分是全球从报纸、杂志和电视转向在线内容的结果。随着定制化的新闻源随时可通过手机获取,还有 Facebook、Reddit、Twitter 和 YouTube 等实时新闻来源,很难想象历史上的替代方案还能再持续多久。令人惊讶的是,这仅仅是互联网上越来越庞大数据量的一部分。
随着全球向使用 HTTP 服务(博客、新闻网站、Netflix 等)消费内容的转变,使用数据驱动分析的机会越来越多。例如,Netflix 会根据用户观看的电影预测他们喜欢什么。这些预测会用来决定推荐的电影。在本章中,我们不会讨论“面向商业”的数据,而是将看到客户端如何将互联网作为数据库来利用。以前从未有过如此多样和丰富的数据能如此轻松地获取。我们将使用网页抓取技术收集数据,并在 Jupyter 中通过交互式可视化进行探索。
交互式可视化是一种数据表现形式,帮助用户通过图表或图形理解数据。交互式可视化帮助开发者或分析师将数据以简单的形式呈现,使非技术人员也能理解。
网页数据抓取
本着将互联网作为数据库的精神,我们可以通过抓取网页内容或与 Web API 接口来获取数据。通常,抓取内容意味着让计算机读取原本是为了人类可读格式显示的数据。这与 Web API 相对,后者是以机器可读的格式传递数据——最常见的是 JSON。
在本主题中,我们将重点讨论网页抓取。具体过程会根据页面和所需内容有所不同。然而,正如我们将看到的,只要我们理解底层的概念和工具,从 HTML 页面抓取任何所需内容其实是相当简单的。在本主题中,我们将使用 Wikipedia 作为示例,从文章中抓取表格内容。然后,我们会将相同的技术应用于抓取其他内容。
从一个完全不同域名页面获取数据。但在此之前,我们将花时间介绍 HTTP 请求。
HTTP 请求简介
超文本传输协议(HTTP)简称为 HTTP,是互联网数据通信的基础。它定义了页面如何被请求以及响应应如何呈现。例如,客户端可以请求一个亚马逊的笔记本电脑销售页面、一个谷歌本地餐厅搜索,或者他们的 Facebook 动态。除了 URL 外,请求还会包含用户代理和可用的浏览器 cookies,位于请求头部内容中。用户代理告诉服务器客户端使用的是哪种浏览器和设备,通常用于提供最符合用户需求的网页响应版本。也许他们最近登录了网页,这类信息会保存在 cookie 中,用于自动登录用户。
这些 HTTP 请求和响应的细节是由 Web 浏览器在后台处理的。幸运的是,今天在使用 Python 等高级语言发起请求时,情况也是如此。对于许多用途,请求头的内容可以被大致忽略。除非另有指定,否则在 Python 中请求 URL 时这些内容会自动生成。不过,为了故障排除和理解我们请求的响应结果,了解 HTTP 的基本概念是很有用的。
HTTP 方法有很多种类型,如 GET、HEAD、POST 和 PUT。前两个用于请求从服务器向客户端发送数据,而后两个则用于向服务器发送数据。
注意
看一下这个 GET 请求示例,其中 User-Agent
为 Mozilla/5.0,代表标准的桌面浏览器。在请求头的其他字段中,我们注意到 Accept
和 Accept-Language
字段,这些字段指定了响应的可接受内容类型和语言。
以下是 HTTP 方法的总结:
-
GET:从指定 URL 获取信息
-
HEAD:从指定 URL 的 HTTP 头部检索元信息
-
POST:发送附加的信息以追加到指定 URL 上的资源
-
PUT:发送附加的信息以替换指定 URL 上的资源
每次我们在浏览器中输入网页地址并按回车时,都会发送一个GET请求。对于网页抓取来说,这通常是我们唯一关心的 HTTP 方法,也是我们在本章中将使用的唯一方法。
一旦请求被发送,服务器可能会返回多种响应类型。这些响应会用 100 到 500 级的代码进行标记,其中代码的第一个数字表示响应类别。可以按以下方式描述这些类别:
-
1xx: 信息性响应,例如,服务器正在处理请求。通常很少看到这种情况。
-
2xx: 成功,例如,页面已正确加载。
-
3xx: 重定向,例如,请求的资源已被移动,我们被重定向到了一个新的 URL。
-
4xx: 客户端错误,例如,请求的资源不存在。
-
5xx: 服务器错误,例如,网站服务器接收到了过多的流量,无法处理请求。
出于网页抓取的目的,我们通常只关心响应类别,即响应代码的第一个数字。然而,每个类别内还存在响应的子类别,这些子类别提供了更精细的分类,帮助我们了解发生了什么。例如,401 代码表示未授权响应,而 404 代码表示页面未找到响应。这一点很重要,因为 404 表示我们请求的页面不存在,而 401 则告诉我们需要登录才能查看该资源。
让我们看看如何在 Python 中发起 HTTP 请求,并在 Jupyter Notebook 中探索其中的一些主题。
在 Jupyter Notebook 中发起 HTTP 请求
现在我们已经讨论了 HTTP 请求的工作原理以及我们应该预期的响应类型,接下来让我们看看如何在 Python 中实现这一点。我们将使用一个名为 urllib
的库来发起 HTTP 请求,但在官方的 Python 文档中,urllib
是一个不同的库。
Requests 是一个非常好的库,可以用于发起简单和复杂的网页请求。它允许对请求的头部、cookie 和授权进行各种定制。它还会跟踪重定向,并提供返回特定页面内容(如 JSON)的方式。此外,它还具有大量的高级功能。然而,它不支持渲染 JavaScript。
通常情况下,服务器返回包含 JavaScript 代码片段的 HTML,这些代码会在浏览器加载时自动运行。当使用 Python 的 Requests 库请求内容时,这些 JavaScript 代码是可见的,但并不会执行。因此,任何通过执行 JavaScript 代码可以改变或创建的元素都会缺失。通常,这不会影响我们获取所需信息的能力,但在某些情况下,我们可能需要渲染 JavaScript 才能正确抓取页面。为此,我们可以使用像 Selenium 这样的库。
这个库的 API 与 Requests 库相似,但它提供了通过 web 驱动程序渲染 JavaScript 的支持。它甚至可以在实时页面上执行 JavaScript 命令,例如,改变文本颜色或滚动到页面底部。
注意
更多信息,请参考:docs.python-requests.org/en/master/user/advanced/
和 selenium-python.readthedocs.io/.
让我们在 Jupyter Notebook 中使用 Requests 库进行一个练习。
练习 14:在 Jupyter Notebook 中使用 Python 处理 HTTP 请求
-
从项目目录启动
NotebookApp
,通过执行 jupyter notebook 命令。导航到lesson-3
目录并打开lesson-3-workbook.ipynb
文件。找到位于顶部的加载包的单元并运行它。我们将请求一个网页,然后检查响应对象。有许多不同的库可以用于发起请求,每个库有许多选择来实现具体的请求方法。我们只使用 Requests 库,因为它提供了优秀的文档、先进的功能和简单的 API。
-
滚动到
子主题 A:HTTP 请求简介
,并运行该部分中的第一个单元以导入 Requests 库。然后,通过运行包含以下代码的单元来准备请求:url = 'https://jupyter.org/' req = requests.Request('GET', url) req.headers['User-Agent'] = 'Mozilla/5.0' req = req.prepare()
我们使用 Request 类准备一个 GET 请求,访问 jupyter.org 的首页。通过将用户代理指定为 Mozilla/5.0,我们请求返回适合标准桌面浏览器的响应。最后,我们准备请求。
-
通过运行包含
req?
的单元,打印“已准备的请求”req 的文档字符串:![图 3.1:打印 req 的文档字符串]](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_01.jpg)
图 3.1:打印 req 的文档字符串
通过查看它的使用方式,我们可以看到如何使用会话发送请求。这类似于打开一个网页浏览器(启动会话),然后请求一个 URL。
-
通过运行以下代码发起请求,并将响应存储在名为 page 的变量中:
with requests.Session() as sess: page = sess.send(req)
这段代码返回 HTTP 响应,如页面变量所引用的那样。通过使用
with
语句,我们初始化一个会话,其作用域仅限于缩进的代码块。这意味着我们无需显式关闭会话,因为它会自动完成。 -
运行 notebook 中的接下来的两个单元,调查响应。页面的字符串表示应该显示 200 状态码响应。这应该与
status_code
属性一致。 -
将响应文本保存到
page_html
变量,并使用page_html[:1000]
查看字符串的头部:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_02.jpg图 3.2:HTML 响应文本
正如预期的那样,响应是 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)Here, we see the HTML rendered as well as possible, given that no JavaScript code has been run and no external resources have loaded. For example, the images that are hosted on the jupyter.org server are not rendered and we instead see the alt text: circle of programming icons, Jupyter logo, and so on.
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_03.jpg
图 3.3:未加载图像时获得的输出
-
让我们将其与可以在 Jupyter 中通过 IFrame 打开的实时网站进行比较。通过运行以下代码来实现:
from IPython.display import IFrame IFrame(src=url, height=800, width=800)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_04.jpg
图 3.4:整个 Jupyter 网站的渲染
在这里,我们可以看到完整的站点渲染,包括 JavaScript 和外部资源。事实上,我们甚至可以点击超链接,并像常规浏览会话一样在 IFrame 中加载这些页面。
-
使用完 IFrame 后,最好将其关闭。这可以防止它占用内存和处理能力。可以通过选择单元格并从 Jupyter Notebook 中的单元格菜单点击 当前输出 | 清除 来关闭它。
回想一下我们是如何使用预设请求和会话将此内容作为字符串请求到 Python 中的。通常也可以使用简写方法来完成此操作。缺点是我们不能对请求头进行太多自定义,但通常这没问题。
-
通过运行以下代码向
www.python.org/
发送请求:url = 'http://www.python.org/' page = requests.get(url) page <Response [200]>
页面(如单元格下方显示的字符串表示)应该指示一个 200 状态码,表示成功响应。
-
运行接下来的两个单元格。在这里,我们打印页面的
url
和历史记录属性。返回的 URL 不是我们输入的 URL;注意其中的差异吗?我们从输入的 URL,
www.python.org/,
被重定向到该页面的安全版本,www.python.org/
。差异体现在协议中的 URL 开头多了一个 s。任何重定向都会保存在历史记录属性中;在这种情况下,我们可以在历史记录中找到一个状态码为 301(永久重定向)的页面,对应于原始请求的 URL。
现在我们已经熟悉了请求的过程,接下来我们将关注 HTML 解析。这可能有些艺术性,因为通常有多种方法可以处理它,而最好的方法通常取决于具体 HTML 的细节。
在 Jupyter Notebook 中解析 HTML
当从网页抓取数据时,在发出请求后,我们必须从响应内容中提取数据。如果内容是 HTML,那么最简单的方法是使用高级解析库,如 Beautiful Soup。并不是说这是唯一的方法;原则上,可以使用正则表达式或 Python 字符串方法(如 split)来提取数据,但采用这些方法会低效且容易出错。因此,通常不推荐这样做,建议使用可靠的解析工具。
为了理解如何从 HTML 中提取内容,了解 HTML 的基本原理是很重要的。首先,HTML 代表超文本标记语言(Hyper Text Markup Language)。像 Markdown 或 XML(可扩展标记语言)一样,它仅仅是用于标记文本的语言。在 HTML 中,显示的文本位于 HTML 元素的内容部分,而元素的属性指定该元素在页面上的显示方式。
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_05.jpg
图 3.5:HTML 的基本块
看看 HTML 元素的结构,如上图所示,我们可以看到内容被包含在开始标签和结束标签之间。在这个例子中,标签是 <p>
(段落);其他常见的标签类型有 <div>
(文本块)、<table>
(数据表格)。
<h1>
(标题)、<img>
(图片)和 <a>
(超链接)。标签有属性,这些属性可以存储重要的元数据。最常见的用途是指定元素文本在页面上的显示方式。这就是 CSS 文件发挥作用的地方。属性还可以存储其他有用的信息,例如 <a>
标签中的超链接 href
,它指定了一个 URL 链接,或者 <img>
标签中的备用 alt 标签,它指定了当图像无法加载时显示的文本。
现在,让我们回到 Jupyter Notebook,并开始解析一些 HTML!虽然在本练习中跟随操作时不必使用,但在实际应用中,使用 Chrome 或 Firefox 的开发者工具来帮助识别感兴趣的 HTML 元素是非常有用的。在接下来的练习中,我们将为您提供如何使用 Chrome 的相关说明。
练习 15:在 Jupyter Notebook 中使用 Python 解析 HTML
-
在
lesson-3-workbook.ipynb
文件中,滚动至子主题 B: 使用 Python 解析 HTML
的顶部。在这个练习中,我们将从维基百科抓取各国的中央银行利率。在开始编码之前,我们先打开包含这些数据的网页。
-
在网页浏览器中访问
en.wikipedia.org/wiki/List_of_countries_by_central_bank_interest_rates
。如果可能的话,请使用 Chrome,因为在接下来的练习中,我们将展示如何使用 Chrome 的开发者工具查看和搜索 HTML。从页面上看,我们几乎只能看到一个列出各国及其利率的大表格。这就是我们将要抓取的数据。
-
返回 Jupyter Notebook,将 HTML 加载为 Beautiful Soup 对象,以便解析。通过运行以下代码来完成此操作:
from bs4 import BeautifulSoup soup = BeautifulSoup(page.content, 'html.parser')
我们使用 Python 默认的 html.parser 作为解析器,但如果需要,也可以使用
lxml
等第三方解析器。通常,在处理像 Beautiful Soup 这样的新对象时,最好通过soup?
来查看文档字符串。然而,在这种情况下,文档字符串并没有提供太多有用的信息。另一个探索 Python 对象的工具是pdir
,它列出了一个对象的所有属性和方法(可以通过 pip installpdir2
安装)。它基本上是 Python 内置dir
函数的格式化版本。 -
通过运行以下代码显示 BeautifulSoup 对象的属性和方法。无论是否安装了
pdir
外部库,都会运行:try: import pdir dir = 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 = h1[0]
,然后通过h1.attrs
打印出 HTML 元素的属性:我们看到这个元素的类和 ID,可以通过 CSS 代码引用它们来定义该元素的样式。
-
通过打印
h1.text
获取 HTML 元素内容(即可见文本)。 -
通过运行以下代码获取页面上的所有图片:
imgs = soup.find_all('img') len(imgs) >> 91
页面上有很多图片,其中大多数是国旗的图片。
-
运行以下代码打印每张图片的源代码:
[element.attrs['src'] for element in imgs if 'src' in element.attrs.keys()]
我们使用列表推导来遍历元素,选择每个元素的
src
属性(只要该属性实际存在)。现在,让我们来抓取表格。我们将使用 Chrome 的开发者工具来找出包含该元素的部分。
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_06.jpg
图 3.6:抓取目标网页上的表格
-
如果还没有完成,请在 Chrome 中打开我们正在查看的 Wikipedia 页面。然后,在浏览器中从 查看 菜单中选择 开发者工具。侧边栏将会打开。HTML 可以从开发者工具的 元素 标签中查看。
-
选择工具侧边栏左上角的小箭头图标。这样,我们可以在页面上悬停,并看到 HTML 元素在侧边栏 元素 部分中的位置:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_07.jpg
图 3.7:定位 HTML 元素的箭头图标
-
悬停在正文部分,查看表格如何被包含在
id="bodyContent"
的 div 中:https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_08.jpg图 3.8:目标网页中表格的 HTML 代码
-
通过运行以下代码选择该
div
:body_content = soup.find('div', {'id': 'bodyContent'})
我们现在可以在 HTML 的这一子集内查找表格。通常,表格按头部
<th>
、行<tr>
和数据条目<td>
组织。 -
通过运行以下代码获取表格头部:
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/>
,这将使得文本清理起来有点困难。 -
通过运行以下代码获取文本:
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
方法获取内容,然后运行替换字符串方法去除由于<br/>
元素导致的换行符。为了获取数据,我们首先执行一些测试,然后将所有数据抓取到一个单元格中。
-
通过运行以下代码获取第二个
<tr>
(行)元素中每个单元格的数据:row_number = 2 d1, d2, d3 = body_content.find_all('tr')[row_number]\ .find_all('td')
我们找到所有的行元素,挑选出第三行,然后在其中找到三个数据元素。
让我们看看结果数据,并了解如何从每一行中解析文本。
-
运行接下来的几个单元格,打印
d1
及其文本属性:图 3.9:打印 d1 及其文本属性图 3.9:打印 d1 及其文本属性
我们在前面得到了一些不需要的字符。可以通过仅搜索
<a>
标签的文本来解决这个问题。 -
运行
d1.find('a').text
来返回该单元格的正确清洗数据。 -
运行接下来的几个单元格,打印
d2
及其文本。这个数据似乎已经足够干净,可以直接转换为浮动值。 -
运行接下来的几个单元格,打印
d3
及其文本:图 3.10:打印 d3 及其文本属性图 3.10:打印
d3
及其文本属性与
d1
类似,我们发现最好只获取span
元素的文本。 -
通过运行以下代码正确解析该表格条目的日期:
d3.find_all('span')[1].text >> '30 June 2016'
-
现在,我们准备通过迭代行元素
<th>
来执行完整的抓取操作。运行以下代码: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
注意
完整代码请参阅以下链接:
bit.ly/2EKMNbV
。我们迭代这些行,忽略任何包含超过三个数据元素的行。这些行将不对应我们感兴趣的表格数据。那些包含三个数据元素的行则被认为是表格中的数据,我们按照测试时确定的方法解析这些文本。
文本解析是在
try/except
语句内部完成的,这样可以捕捉到任何错误并跳过该行而不停止迭代。任何由于该语句而引发错误的行应当被查看。这些数据可以手动记录,或通过修改抓取循环并重新运行来解决。在这种情况下,为了节省时间,我们会忽略任何错误。 -
通过运行
print(data[:10])
来打印抓取数据列表的前十项:>> [['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 文件中:
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))
请注意,我们使用分号来分隔字段。
活动 3:使用 Jupyter Notebooks 进行网页抓取
你应该已经完成了本章的前一个练习。
在本活动中,我们将获取每个国家的人口数据。接下来,在下一个话题中,这些数据将与前一个练习中抓取的利率数据一起进行可视化。
我们在本活动中查看的页面可以通过以下链接访问:www.worldometers.info/world-population/population-by-country/
。
我们的目标是将网页抓取的基础应用到一个新网页,并抓取更多的数据。
注意
由于本文档创建时该页面内容可能已经发生变化。如果该 URL 不再指向国家人口表格,请改用此 Wikipedia 页面:en.wikipedia.org/wiki/List_of_countries_by_population(United_Nations)
。
为了实现这一目标,需要执行以下步骤:
-
从网页抓取数据。
-
在
lesson-3-workbook.ipynb
Jupyter Notebook 中,滚动至Activity A: 使用 Python 进行网页抓取
。 -
设置
url
变量并在笔记本中加载我们的页面的 IFrame。 -
通过选择单元格并点击 Jupyter Notebook 中 当前输出 | 清除,从单元格菜单中清除 IFrame。
-
请求页面并将其加载为
BeautifulSoup
对象。 -
打印页面的 H1 标签。
-
获取并打印表格的标题。
-
选择前三列并解析文本。
-
获取样本行的数据。
-
我们有多少列数据?打印
row_data
的长度。 -
打印行的前几个元素。
-
选择数据元素 d1、d2 和 d3。
-
查看
row_data
输出,我们可以发现如何正确地解析数据。选择第一个数据元素中<a>
元素的内容,然后直接从其他元素中获取文本。 -
抓取并解析表格数据。
-
打印抓取数据的头部。
-
最后,将数据保存为 CSV 文件以便后续使用。
注意
详细的步骤及解决方案已在附录 A(第 160 页)中给出。
总结一下,我们已经看到了如何使用 Jupyter Notebooks 进行网页抓取。我们从本章开始学习了 HTTP 方法和状态码。接着,我们使用 Requests 库实际执行了 Python 中的 HTTP 请求,并看到了 Beautiful Soup 库如何用于解析 HTML 响应。
我们的 Jupyter Notebook 证明是进行这类工作的一个很好的工具。我们能够探索网页请求的结果,并实验各种 HTML 解析技术。我们还能够渲染 HTML,甚至在笔记本内加载网页的实时版本!
在本章的下一个主题中,我们将转向一个全新的话题:互动可视化。我们将学习如何在笔记本中创建并展示互动图表,并将这些图表用作探索我们刚刚收集的数据的方式。
互动可视化
可视化是从数据集提取信息的有力工具。例如,通过柱状图,与查看表格中的值相比,很容易区分出值的分布。当然,正如我们在本书中之前看到的,它们还可以用来研究数据集中的模式,这些模式在其他情况下可能非常难以识别。此外,它们还可以帮助向不熟悉的人解释数据集。例如,如果包含在博客文章中,它们可以提高读者的兴趣并用来打破文本块。
在考虑交互式可视化时,其优点类似于静态可视化,但因为它们允许观众进行主动探索,所以更加增强了体验。它们不仅让观众能够回答自己对数据的疑问,同时在探索过程中还会激发出新的问题。这不仅能为独立方如博客读者或同事带来好处,也能帮助创作者,因为它允许在不需要修改任何代码的情况下轻松进行数据的临时详细探索。
在本主题中,我们将讨论并展示如何在 Jupyter 中使用 Bokeh 构建交互式可视化。然而,在此之前,我们将简要回顾 pandas DataFrame,它在使用 Python 进行数据可视化时起着重要作用。
构建 DataFrame 来存储和组织数据
正如我们在本书中反复看到的,pandas 是使用 Python 和 Jupyter Notebook 进行数据科学不可或缺的一部分。DataFrame 提供了一种组织和存储标记数据的方法,但更重要的是,pandas 提供了节省时间的处理方法,用于在 DataFrame 中转换数据。本书中我们已经看到的例子包括删除重复项、将字典映射到列、在列上应用函数和填充缺失值。
关于可视化,DataFrame 提供了创建各种 matplotlib 图形的方法,包括 df.plot.barh()
、df.plot.hist()
等。之前,交互式可视化库 Bokeh 依赖 pandas DataFrame 来生成其 高级图表。这些图表的工作方式类似于我们在上一章中看到的 Seaborn,即将 DataFrame 传递给绘图函数,并指定要绘制的列。然而,Bokeh 的最新版本已不再支持这种行为。现在,图表的创建方式与 matplotlib 类似,数据可以存储在简单的列表或 NumPy 数组中。讨论的重点是,DataFrame 并不是绝对必要的,但它仍然对在可视化前整理和处理数据非常有帮助。
练习 16:构建和合并 Pandas DataFrame
让我们直接进入一个练习,继续处理我们之前抓取的国家数据。回想一下,我们提取了各国的中央银行利率和人口数据,并将结果保存到 CSV 文件中。我们将从这些文件中加载数据,并将它们合并成一个 DataFrame,随后用于交互式可视化。
-
在 Jupyter Notebook 的
lesson-3-workbook.ipynb
中,滚动到Topic B
部分的Subtopic A: 构建 DataFrame 来存储和组织数据
子主题。我们首先将从 CSV 文件加载数据,使其恢复到抓取后的状态。这将允许我们练习从 Python 对象构建 DataFrame,而不是使用
pd.read_csv
函数。注意
使用
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 %']]
现在,数据已经变成了标准的 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,让我们打印头部和尾部,并列出数据类型。
-
当显示整个 DataFrame 时,默认的最大行数为 60(对于版本 0.18.1)。让我们通过运行以下代码将其减少到 10 行:
pd.options.display.max_rows = 10
-
通过运行以下代码显示利率 DataFrame 的头部和尾部:
df_int_rates
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_11.jpg
图 3.11:各国利率表
-
通过运行以下代码打印数据类型:
df_int_rates.dtypes >> Country or currency union object >> Central bank interest rate (%) object >> Date of last change object >> dtype: object
Pandas 已将每一列指定为字符串数据类型,这是合理的,因为输入变量都是字符串。我们需要将这些列分别转换为字符串、浮动数值和日期时间类型。
-
通过运行以下代码将数据转换为正确的数据类型:
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
如图所示,一切现在都处于正确的格式中。
-
让我们对另一个数据框应用相同的过程。运行接下来的几个单元,重复之前针对
df_populations
的步骤:df_population
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_12.jpg
图 3.12:按国家划分的人口表格
然后,运行此代码:
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)
为了将数字列转换为浮动类型,我们必须首先对这些字符串进行一些修改。在这种情况下,我们去除了人口中的逗号,并去掉了年变化列中的百分号,使用了字符串方法。
现在,我们将根据每行的国家名称合并数据框。请记住,这些仍然是从网络抓取的原始国家名称,所以可能需要进行一些字符串匹配的工作。
-
通过运行以下代码合并数据框:
df_merge = pd.merge(df_populations, df_int_rates, left_on='Country (or dependency)', right_on='Country or currency union', how='outer' df_merge
我们将左侧数据框中的人口数据与右侧数据框中的利率数据进行合并,在国家列上执行外部匹配。如果两个数据框没有交集,将会出现
NaN
值。 -
为了节省时间,让我们先看看人口最多的国家,看看我们是否错过了匹配。理想情况下,我们应该检查所有内容。通过运行以下代码查看人口最多的国家:
df_merge.sort_values('Population (2017)', ascending=False)\ .head(10)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_13.jpg
图 3.13:人口最多国家的表格
看起来美国没有匹配上。这是因为在利率数据中,它被列为United States。让我们来解决这个问题。
-
通过运行以下代码修正人口表中美国的标签:
col = 'Country (or dependency)' df_populations.loc[df_populations[col] == 'U.S.'] = 'United States'
我们使用
loc
方法重命名人口数据框中的国家列,通过定位该行来进行操作。现在,让我们正确地合并数据框。
-
重新根据国家名称合并数据框,但这次使用内连接合并,以去除
NaN
值:df_merge = pd.merge(df_populations, df_int_rates, left_on='Country (or dependency)', right_on='Country or currency union', how='inner')
-
我们在合并后的数据框中得到了两列相同的列。通过运行以下代码删除其中一列:
del df_merge['Country or currency union']
-
通过运行以下代码重命名列:
name_map = {'Country (or dependency)': 'Country', 'Population (2017)': 'Population', 'Central bank interest rate (%)': 'Interest rate'} df_merge = df_merge.rename(columns=name_map)
我们得到以下合并并清理后的数据框:
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_14.jpg
图 3.14:清理和合并表格后的输出
-
现在我们已经将所有数据整理成一个漂亮的表格,可以进入有趣的部分了:可视化它。让我们将这个表格保存到 CSV 文件中以供以后使用,然后继续讨论如何使用 Bokeh 创建可视化。
使用以下代码将合并后的数据写入 CSV 文件以供以后使用:
df_merge.to_csv('../data/countries/merged.csv', index=False)
Bokeh 简介
Bokeh 是一个 Python 的交互式可视化库。它的目标是提供类似于 D3 的功能,D3 是 JavaScript 中流行的交互式可视化库。Bokeh 的工作方式与 D3 有很大的不同,这并不奇怪,因为 Python 和 JavaScript 之间存在差异。总体来说,Bokeh 要简单得多,而且不像 D3 那样允许进行大量定制。然而,这也正是它的优势所在,因为它更容易使用,并且仍然拥有一套出色的功能,我们将在本节中进行探索。
让我们通过一个简单的练习来快速了解 Jupyter Notebook,并通过示例介绍 Bokeh。
注意
网上有关于 Bokeh 的良好文档,但许多文档已经过时。例如,使用 Google 搜索"Bokeh 条形图"时,通常会找到关于不再存在的遗留模块的文档,例如,曾通过bokeh.charts
提供的高级绘图工具(在 0.12.0 版本之前)。这些工具与 Seaborn 的绘图函数类似,接受 pandas DataFrame 作为输入。去除高级绘图工具模块使 Bokeh 变得更加简洁,并将为今后的集中开发提供更多空间。现在,绘图工具主要集中在bokeh.plotting
模块中,下一次练习和随后的活动中将会看到这一点。
练习 17:Bokeh 交互式可视化简介
我们将加载所需的 Bokeh 模块,并展示一些可以用 Bokeh 创建的简单交互式图形。请注意,本书中的示例是基于 Bokeh 版本 0.12.10 设计的。
-
在
lesson-3-workbook.ipynb
Jupyter notebook 中,滚动至子主题 B:Bokeh 简介
。 -
和 scikit-learn 一样,Bokeh 模块通常是按需加载的(与 pandas 不同,后者会一次性加载整个库)。通过运行以下代码导入一些基本的绘图模块:
from bokeh.plotting import figure, show, output_notebook output_notebook()
我们需要运行
output_notebook()
才能在 Jupyter notebook 中渲染交互式可视化。 -
通过运行以下代码生成随机数据以进行绘图:
np.random.seed(30) data = pd.Series(np.random.randn(200), index=list(range(200)))\ .cumsum() x = data.index y = 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)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_15.jpg
图 3.15:示例数据图
我们实例化图形,使用变量
p
引用它,然后绘制一条线。在 Jupyter 中运行后,会显示一个交互式图形,并在右侧提供各种选项。前三个选项(截至版本 0.12.10)是平移、框选缩放和滚轮缩放。尝试这些功能,并实验它们的工作方式。使用重置选项重新加载默认的绘图范围。
-
可以使用
figure
的其他方法创建其他图表。通过运行以下代码绘制散点图,将前面代码中的line
替换为circle
:size = np.random.rand(200) * 5 p = 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)
https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_16.jpg
](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_16.jpg)
图 3.16:一个示例散点图
在这里,我们使用一组随机数字来指定每个圆圈的大小。交互式可视化的一个非常吸引人的特点是工具提示。它是一种悬停工具,允许用户通过悬停在某个点上查看该点的信息。
-
为了添加这个工具,我们将使用一种稍微不同的方法来创建图表。这将需要我们导入几个新的库。运行以下代码:
from bokeh.plotting import ColumnDataSource from bokeh.models import HoverTool
这次,我们将创建一个数据源并传递给绘图方法。这个数据源可以包含元数据,通过悬停工具在可视化中显示。
-
通过运行以下代码,创建随机标签并绘制带有悬停工具的交互式可视化:
source = ColumnDataSource(data=dict( x=x, y=y, ... ... source=source, legend='Random dots') show(p)
注意
完整代码请参考:
bit.ly/2RhpU1r
。https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_17.jpg
](https://github.com/OpenDocCN/freelearn-ds-pt5-zh/raw/master/docs/app-ds-py-jpt/img/C13018_03_17.jpg)
图 3.17:带标签的随机散点图
我们通过将键值对字典传递给
ColumnDataSource
构造函数来定义图表的数据源。这个数据源包括每个点的X位置、Y位置以及大小,同时还包括随机字母A
、B
或C
,这些字母作为悬停工具的标签,悬停时会显示每个点的大小。bokeh.plotting.figure
。 -
通过运行以下代码,将平移、缩放和重置工具添加到图表中:
from bokeh.models import PanTool, BoxZoomTool, WheelZoomTool, ResetTool ... ... legend='Random dots') show(p)
这段代码与之前显示的完全相同,唯一不同的是
tools
变量,它现在引用了我们从 Bokeh 库导入的几个新工具。
我们将在此处停止介绍性练习,但将在接下来的活动中继续创建和探索图表。
活动 4:使用交互式可视化探索数据
你应该已经完成了之前的练习,才能继续完成此活动。
我们将继续使用 Bokeh,从上一个练习结束的地方开始,只不过这次我们不再使用之前看到的随机生成数据,而是使用我们在本章第一部分从网页抓取的数据。我们的目标是使用 Bokeh 来创建抓取数据的交互式可视化。
为了做到这一点,我们需要执行以下步骤:
-
在
lesson-3-workbook.ipynb
文件中,滚动到Activity B: 使用 Bokeh 进行交互式可视化
部分。 -
加载之前抓取、合并并清洗过的网页数据
-
通过显示 DataFrame 来回顾数据的样子。
-
绘制一个人口与利率之间关系的散点图。
-
在数据中,我们看到一些明显的异常值,具有较高的值。将鼠标悬停在这些点上查看它们是什么。选择框选缩放工具,并调整视图窗口,以便更好地看到大部分数据。
-
一些人口较少的国家似乎有负利率。选择滚轮缩放工具,使用它放大该区域。若需要,使用平移工具重新定位图表,确保负利率样本在视野内。悬停在这些样本上,查看它们对应的国家。
-
向 DataFrame 中添加一个最后变更年份列,并根据最后利率变化的日期为其添加颜色。
-
创建一个地图,将最后变更日期分成不同的颜色类别。
-
创建彩色可视化。
-
寻找模式时,放大查看低人口国家。
-
运行以下代码,将利率绘制为与年同比人口变化的函数。
-
确定先前绘制关系的最佳拟合线。
-
重新绘制前一步得到的输出,并添加最佳拟合线。
-
使用缩放工具探索图表,并悬停在有趣的样本上。
注意
详细步骤及解决方案展示在附录 A(第 163 页)。
总结
在本章中,我们抓取了网页表格,然后使用互动式可视化来研究数据。
我们首先了解了 HTTP 请求的工作原理,重点讨论了 GET 请求及其响应状态码。然后,我们进入 Jupyter Notebook,使用 Python 的 Requests 库发出了 HTTP 请求。我们看到了 Jupyter 如何在笔记本中渲染 HTML,以及实际可以交互的网页。发出请求后,我们看到 Beautiful Soup 如何解析 HTML 中的文本,并使用该库抓取表格数据。
在抓取了两张数据表后,我们将其存储在 pandas DataFrame 中。第一张表包含每个国家的中央银行利率,第二张表包含人口数据。我们将这两张表合并成一个表,然后用它来创建互动式可视化图表。
最后,我们使用 Bokeh 在 Jupyter 中渲染了互动式可视化。我们学习了如何使用 Bokeh API 创建各种自定义图表,并制作了具有特定交互功能(如缩放、平移和悬停)的散点图。在自定义方面,我们明确展示了如何为每个数据样本设置点的半径和颜色。
此外,在使用 Bokeh 探索抓取的人口数据时,我们利用了工具提示(tooltip)来显示国家名称和相关数据,当鼠标悬停在数据点上时。
恭喜你完成了使用 Jupyter Notebooks 进行数据科学入门课程!无论你之前有多少 Jupyter 和 Python 的经验,你已经掌握了一些实用且可以应用于实际数据科学的技能!
在结束之前,让我们快速回顾一下本书中涵盖的主题。
第一章介绍了 Jupyter Notebook 平台,我们涵盖了所有基础知识。我们了解了界面以及如何使用和安装魔法函数。接着,我们介绍了将要使用的 Python 库,并通过波士顿住房数据集进行了一次探索性分析。
在第二章中,我们专注于使用 Jupyter 进行机器学习。我们首先讨论了制定预测分析计划的步骤,然后介绍了几种不同类型的模型,包括 SVM、KNN 分类器和随机森林。
在处理员工离职数据集时,我们应用了数据清洗方法,并训练了模型来预测员工是否离职。我们还探讨了更高级的话题,如过拟合、k 折交叉验证和验证曲线。
最后,在第三章中,我们暂时从数据分析转向了数据收集,使用了网页抓取技术,并学习了如何在 Jupyter 中进行 HTTP 请求以及解析 HTML 响应。随后,我们通过使用交互式可视化工具来探索我们收集的数据,完成了本书内容。