Jupyter5-学习指南-全-

Jupyter5 学习指南(全)

原文:annas-archive.org/md5/9a12d92ec3c259b9feace22b7716b78f

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

学习 Jupyter 讨论了如何使用 Jupyter 记录脚本并生成数据分析项目的结果。Jupyter 让数据科学家能够记录完整的分析过程,类似于其他科学家使用实验室笔记本记录测试、进展、结果和结论的方式。Jupyter 支持多种操作系统,本书将介绍在 Windows 和 macOS 中使用 Jupyter,以及满足您特定需求所需的各种步骤。通过添加语言引擎,Jupyter 支持多种脚本语言,使用户能够以原生方式使用特定的脚本。

本书适合谁阅读

本书是为那些希望以自然编程的方式向他人展示软件解决方案的读者编写的。Jupyter 提供了一种机制,可以执行多种语言并直接存储结果以供显示,就像用户在自己的机器上运行了这些脚本一样。

本书内容

第一章,Jupyter 入门,探讨了笔记本中可用的各种用户界面元素。我们将学习如何在 macOS 或 PC 上安装软件。我们将揭示笔记本的结构。我们将看到开发笔记本时的典型工作流程。我们将逐步了解笔记本中的用户界面操作。最后,我们将查看一些高级用户为笔记本提供的配置选项。

第二章,Jupyter Python 脚本编写,带您了解一个简单的笔记本及其基础结构。然后,我们将看到一个使用 pandas 的示例,并查看图形示例。最后,我们将看到一个使用 Python 脚本生成随机数的示例。

第三章,Jupyter R 脚本编写,增加了在我们的 Jupyter Notebook 中使用 R 脚本的功能。我们将添加一个不包含在标准 R 安装中的 R 库,并编写一个 Hello World R 脚本。接着,我们将了解 R 数据访问内置库以及一些自动生成的简单图形和统计数据。我们将使用 R 脚本生成几种不同方式的 3D 图形。然后,我们将进行标准的聚类分析(我认为这是 R 的基本用途之一),并使用其中一个预测工具。我们还将构建一个预测模型并测试其准确性。

第四章,Jupyter Julia 脚本,增加了在 Jupyter Notebook 中使用 Julia 脚本的功能。我们将添加一个不包含在标准 Julia 安装中的 Julia 库。我们将展示 Julia 的基本特性,并概述在 Jupyter 中使用 Julia 时遇到的一些局限性。我们将使用一些可用的图形包来展示图形。最后,我们将看到并行处理的实际应用,一个小的控制流示例,以及如何为 Julia 脚本添加单元测试。

第五章,Jupyter Java 编程,讲解了如何将 Java 引擎安装到 Jupyter 中。我们将看到 Java 在 Jupyter 中的不同输出展示示例。接着,我们将研究如何使用可选字段。我们将看到 Java 在 Jupyter 中的编译错误。接下来,我们将看到几个 lambda 示例。我们将使用集合完成多个任务。最后,我们将为一个标准数据集生成汇总统计。

第六章,Jupyter JavaScript 编程,展示了如何在 Jupyter Notebook 中添加 JavaScript。我们将看到在 Jupyter 中使用 JavaScript 的一些局限性。我们将查看一些典型的 Node.js 编程包的示例,包括图形、统计、内置 JSON 处理,以及使用第三方工具创建图形文件。我们还将看到如何在 Jupyter 中使用 Node.js 开发多线程应用程序。最后,我们将使用机器学习开发决策树。

第七章,Jupyter Scala,讲解了如何为 Jupyter 安装 Scala。我们将使用 Scala 编程来访问大型数据集。我们将看到 Scala 如何操作数组。我们将生成 Scala 中的随机数。这里有高阶函数和模式匹配的示例。我们将使用 case 类。我们将看到 Scala 中不变性的示例。我们将使用 Scala 包构建集合,并将讨论 Scala 特质。

第八章,Jupyter 与大数据,讨论了如何通过 Python 编程在 Jupyter 中使用 Spark 功能。首先,我们将在 Windows 和 macOS 机器上安装 Spark 插件。我们将编写一个初步的脚本,仅读取文本文件中的行。然后,我们将进一步处理,计算文件中的单词数。我们将为结果添加排序。这里有一个估算 pi 的脚本。我们将分析网页日志文件中的异常。我们将确定一组素数,并分析文本流中的一些特征。

第九章,互动小部件,讲解了如何向我们的 Jupyter 安装添加小部件。我们将使用 interact 和 interactive 小部件来生成多种用户输入控件。接下来,我们将深入研究小部件包,探讨可用的用户控件、容器中的属性以及控件发出的事件,并学习如何构建控件容器。

第十章,共享和转换 Jupyter Notebooks,介绍了如何在笔记本服务器上共享笔记本。我们将把笔记本添加到 Web 服务器并通过 GitHub 分发。我们还将探讨如何将笔记本转换为不同格式,例如 HTML 和 PDF。

第十一章,多用户 Jupyter Notebooks,展示了如何让多个用户同时使用一个笔记本。我们将展示共享错误发生的例子。我们将安装一个解决该问题的 Jupyter 服务器,并使用 Docker 缓解这个问题。

第十二章,接下来怎么办?,探讨了未来可能会融入 Jupyter 的一些功能。

为了最大限度地利用本书

本书中的步骤假设你拥有一台现代的 Windows 或 macOS 设备,并且能够访问互联网。书中有几个步骤需要安装软件,因此你需要具有该设备的管理员权限才能进行操作。

本书的预期是,你有一个或多个自己喜欢的实现语言,想在 Jupyter 上使用。

下载示例代码文件

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

你可以按照以下步骤下载代码文件:

  1. 请在 www.packtpub.com 上登录或注册。

  2. 选择 SUPPORT 标签。

  3. 点击代码下载 & 勘误。

  4. 在搜索框中输入书名,并按照屏幕上的指示操作。

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

  • WinRAR/7-Zip(适用于 Windows)

  • Zipeg/iZip/UnRarX(适用于 Mac)

  • 7-Zip/PeaZip(适用于 Linux)

本书的代码包也托管在 GitHub 上,地址为 github.com/PacktPublishing/Learning-Jupyter-5-Second-Edition。如果代码有更新,更新内容会直接发布在现有的 GitHub 仓库中。

我们还提供了来自丰富书籍和视频目录中的其他代码包,地址是 github.com/PacktPublishing/。快来看看吧!

使用的约定

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

CodeInText:表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账户名。以下是一个示例:“默认文件名 untitled1.txt 是可编辑的。”

代码块如下设置:

var mycell = Jupyter.notebook.get_selected_cell();
var cell_config = mycell.config;
var code_patch = {
    CodeCell:{
      cm_config:{indentUnit:2}
    }
 }
cell_config.update(code_patch)

任何命令行输入或输出的书写方式如下:

jupyter trust /path/to/notebook.ipynb

粗体:表示新术语、重要词汇或屏幕上显示的词语。例如,菜单或对话框中的词汇会以这样的方式出现在文本中。以下是一个示例:“显示了三个选项卡:文件、运行和集群。”

警告或重要说明如下所示。

提示和技巧以此方式展示。

联系我们

我们始终欢迎读者的反馈。

一般反馈:请通过电子邮件发送至 feedback@packtpub.com,并在邮件主题中注明书名。如果您对本书的任何内容有疑问,请通过 questions@packtpub.com 与我们联系。

勘误:尽管我们已尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在本书中发现任何错误,请联系我们。请访问 www.packtpub.com/submit-errata,选择您的书籍,点击“勘误提交表单”链接,并填写相关细节。

盗版:如果您在互联网上发现任何我们作品的非法复制版本,恳请您提供该网址或网站名称。请通过 copyright@packtpub.com 与我们联系,并附上该材料的链接。

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

评审

请留下评审。在您阅读并使用本书后,为什么不在购买该书的网站上留下您的评论呢?潜在读者可以根据您的客观意见做出购买决策,Packt 也能了解您对我们产品的看法,而我们的作者也能看到您对其书籍的反馈。谢谢!

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

第一章:Jupyter 简介

Jupyter 是一款工具,允许数据科学家记录他们的完整分析过程,类似于其他科学家使用实验室笔记本来记录实验、进展、结果和结论。

Jupyter 最初是作为 IPython 项目的一部分开发的。IPython 项目用于提供交互式在线访问 Python。随着时间的推移,它变得也可以以相同方式与其他数据分析工具(如 R)交互。随着这一脱离 Python 的过程,该工具发展成了现在的 Jupyter 形式。IPython 仍然是一个可供使用的活跃工具。Jupyter 这个名称本身来源于 Julia、Python 和 R 的结合。

Jupyter 可以作为 web 应用程序从多个地方获取。它也可以在本地通过多种安装方式使用。在本书中,我们将探讨如何在 macOS 和 Windows PC 上使用 Jupyter,以及如何通过其他提供商使用 Jupyter 进行互联网操作。

在 Jupyter 5.0 中,以下内容得到了显著增强:

  • 单元格标签

  • 自定义键盘快捷键

  • 在 Notebooks 之间复制和粘贴单元格

  • 为表格提供更具吸引力的默认样式

在本章中,我们将讨论以下主题:

  • 初次了解 Jupyter

  • 安装 Jupyter

  • Notebook 结构

  • Notebook 工作流程

  • 基本的 Notebook 操作

  • Jupyter 的安全性

  • Jupyter 的配置选项

初次了解 Jupyter

这是使用 Jupyter 时的一个示例首页(此截图来自 Windows 计算机):

你应该熟悉环境。Jupyter 用户界面有许多组件:

  • 产品标题“Jupyter”位于左上角(如预期)。该 logo 和标题是可点击的,点击后将返回到 Jupyter Notebook 的主页。

  • 显示了三个选项卡:文件、运行和集群:

  • “文件”选项卡显示当前页面目录中的文件列表(稍后会详细描述)。

  • “运行”选项卡展示了另一个屏幕,显示当前正在运行的进程和 Notebook。终端和 Notebook 的下拉列表会列出其正在运行的项目:

  • “集群”选项卡展示了另一个屏幕,显示可用集群的列表。这个话题将在后续章节中讨论:

  • 在屏幕的右上角,有三个按钮:上传、创建(菜单)和刷新 Notebook 列表按钮。

  • 上传按钮用于将文件添加到 Notebook 空间。你也可以像处理文件一样直接拖拽文件。同样,你也可以将 Notebook 拖拽到特定的文件夹中。

  • 顶部带有“新建”的菜单呈现了一个进一步的菜单,显示了已安装的不同笔记本引擎(我之前安装了 Jupyter,这些不是默认值)Javascript(Node.js)、Julia 0.6.1、Python 2(本书中不涵盖)和 Python 3。其他的菜单项有文本文件文件夹终端

  • 文本文件选项用于将文本文件添加到当前目录。Jupyter 会为你打开一个新的浏览器窗口,运行一个文本编辑器。输入的文本会自动保存,并显示在你的笔记本文件和目录显示中:

默认的文件名untitled1.txt是可以编辑的。请注意,文件名与笔记本的标题对应。

  • 文件夹选项创建一个名为“未命名文件夹”的新文件夹。记住,所有文件和文件夹名称都是可以编辑的:

  • 终端选项用于打开一个新的终端(命令)窗口。Windows 机器上的显示效果如下:

  • Python 3选项用于启动一个新的 Python 3 笔记本。界面如下所示。你可以完全编辑你的脚本文件,包括保存为新文件。你还拥有一个完整的 IDE 来编写 Python 脚本:

请注意,像文本文件文件夹选项一样,你已经在笔记本中创建了一个 Python 脚本文件,并且它正在运行!(你可以在 Jupyter 的主页显示中看到这一点):

  • 刷新笔记本列表按钮用于更新显示。其实并不太需要,因为显示会自动响应底层文件结构的任何变化。

  • 在文件标签的顶部有一个复选框,一个下拉菜单和一个首页按钮。

  • 复选框用于切换项目列表中的所有复选框。

  • 下拉菜单呈现一个可用选择的列表,即文件夹所有笔记本运行中文件,如下所示:

  • 文件夹选择将选择显示中的所有文件夹,并在小框中显示文件夹的数量。

  • 所有笔记本选择将把计数更改为笔记本的数量,并为你提供五个选项:

    • 复制(选定的笔记本)

    • 关闭(选定的笔记本)

    • 查看(选定的笔记本)

    • 编辑(选定的笔记本)

    • 删除(垃圾桶图标;选定的笔记本)

  • 你可以在下面的截图中看到它们:

  • 运行选择将选择显示中的所有运行脚本,并更新计数为所选脚本的数量:

  • 文件选择将选择笔记本显示中的所有文件,并相应地更新计数。

  • “主页”按钮将带您返回到 Notebook 的主页屏幕。

  • 每个项目的左侧都有一个复选框、一个图标和项目名称:

  • 复选框用于构建一组文件以供操作。

  • 图标指示了项目的类型。在这种情况下,所有的项目都是文件夹。

  • 项目的名称对应于对象的名称。在这种情况下,文件名与磁盘上的文件名一致。

安装 Jupyter

Jupyter 需要安装 Python(毕竟它是基于 Python 语言的)。有一些工具可以通过图形界面自动安装 Jupyter(并可选地安装 Python)。在这里,我们将展示如何使用 Anaconda 安装 Jupyter,Anaconda 是一个用于分发软件的 Python 工具。

首先,您需要安装 Anaconda。它可以在 Windows 和 macOS 环境下使用。从 www.continuum.io/(生产 Anaconda 的公司)下载可执行文件并运行,以安装 Anaconda。务必选择使用 Python 3.x 的 Anaconda 版本,而不是 Python 2.x 版本。该软件提供了常规的安装设置过程,如下所示:

安装过程会经过常规的步骤,要求您同意分发权利许可:

标准的 Windows 安装允许您决定所有用户是否都能在该机器上运行新软件。如果您与其他具有不同权限级别的用户共享一台机器,您可以根据需要选择适当的操作:

点击“下一步”后,它会要求您选择软件的安装位置(我几乎总是保持默认路径):

Anaconda 还会调整您的文件路径,使得您可以通过下一个对话框在计算机的任何位置访问 Anaconda,具体如下:

安装过程将开始。这可能需要一些时间,具体取决于您的计算机配置和网络连接:

最终,您会看到安装完成的屏幕,如下所示:

在 Windows 上,Anaconda 利用了 Visual Development Environment 的半内建功能,以本地方式访问 Windows 服务。它会通过以下对话框请求许可:

现在我们已经真正安装了 Jupyter:

Anaconda 将启动。Anaconda 是一个出色的包装程序,包含了多个工具的分发。对我们来说,重要的工具是 Jupyter。Anaconda 显示了可用工具的状态,是否需要安装,并为每个工具提供了一个起始位置。

你可以直接通过在终端窗口中使用 > jupyter notebook 命令来启动 Jupyter。

如果我们从 Anaconda 界面选择 Jupyter,它将在一个新的浏览器窗口中启动 Jupyter:

当 Jupyter 正在运行时,我们可以通过使用文件 | 关于 菜单来获取一些关于安装的详细信息,该菜单会提供一个对话框,显示如下 Jupyter 安装的详细信息:

如果你直接从命令行启动 Jupyter,Jupyter 将在一个新的浏览器窗口中打开,你会看到一些日志条目显示在终端窗口中,显示你使用过程中的进度:

请注意,日志的最后一行是停止服务器时必须使用的指令(在运行服务器的命令行窗口中按 Ctrl + C)。

如果你在该窗口按 Ctrl + C,Jupyter 服务器将优雅地关闭:

[W 17:26:36.688 NotebookApp] 404 GET /favicon.ico (::1) 62.00ms referer=None 
[W 17:26:36.750 NotebookApp] 404 GET /favicon.ico (::1) 0.00ms referer=None 
[I 17:28:24.891 NotebookApp] Interrupted... 
[I 17:28:24.891 NotebookApp] Shutting down kernels 

你会注意到,Anaconda 包已经被安装在你的应用程序菜单中以供进一步使用:

Notebook 结构

Jupyter Notebook 本质上是一个包含多个注释的 JSON 文件。Notebook 的主要部分如下:

  • 元数据:用于设置和显示 Notebook 的定义数据字典

  • Notebook 格式:用于创建 Notebook 的软件版本号(版本号用于向后兼容)

  • 单元格列表:有不同类型的单元格,包括 Markdown(显示)、代码(执行)和输出(代码类型单元格的输出)

Notebook 工作流

典型的工作流程如下:

  • 为项目或数据分析创建一个新的 Notebook。

  • 添加你的分析步骤、编码和输出。

  • 用组织性和呈现性的 Markdown 包围你的分析,以讲述一个完整的故事。

  • 互动式 Notebook(包括小部件和显示模块)将由其他人使用,通过修改参数和数据来记录他们更改的效果。你的 Markdown 会展示用户可能想要调查的案例以及可能的结果。

基本的 Notebook 操作

本节将描述你可以在 Jupyter Notebook 上执行的不同操作。大多数操作是菜单功能,将相应地更改你的显示。

文件操作

让我们一起浏览基本的文件操作。

在文件选项卡中,我们可以看到当前 Notebook/磁盘文件夹中的文件和文件夹列表。如果我们选择(勾选)其中一个文件,我们会看到左上角的菜单发生变化:

现在我们有复制、重命名和删除(垃圾桶图标)的选项。请注意,选中的文件数(1)也会显示在框中。

复制

如果我们点击重复按钮,将出现一个确认提示框,显示已选中的文件名用于复制:

取消将关闭对话框。重复将创建另一个文件副本,并附加副本编号,如下图所示。原始文件名已被使用,并在文件名中添加了-Copyn,其中 n 是副本编号。请注意,新文件中保持了原始文件扩展名.py

重命名

同样,如果我们点击重命名按钮,另一个对话框将提示输入新的文件名。主文件名已被高亮显示,因为假设你希望保留文件扩展名,因为文件类型没有变化:

删除

我们还可以通过点击垃圾桶图标删除文件。这会弹出一个确认对话框如下所示。我喜欢他们把删除按钮的背景改为红色,以确保你不会轻易点击它:

在屏幕的右上角,我们有上传和新建选项。

上传

上传按钮在笔记本存储在网页服务器上时更有意义。在桌面上运行时,它可以让你轻松地将文件从笔记本的一个部分移动到另一个部分。如果点击这个按钮,你将看到一个文件选择对话框。下面的截图是特定于 Windows 环境的,但在 macOS 上会显示类似的界面。选择文件后,它将被添加到你的笔记本空间中:

新建文本文件

如果我们选择创建一个文本文件,我们会看到一个新的浏览器面板出现在 Jupyter 文本编辑器中(我已缩小屏幕尺寸以便显示适应本书的边界):

在这个屏幕上有几个重点:

  • 我们位于一个新的浏览器面板中(笔记本显示仍然位于其他标签中)。

  • 新文件的名字是untitled1.txt。使用与复制相同的约定,新文件名以untitled.txt开头,并根据需要增加数字。

  • 有趣的是,它会显示文件创建的时间。

  • 在右上角,我们看到“纯文本”。所以,我们可能会期待看到其他文件类型的描述。

  • 我们有一个新菜单,包括文件、编辑、视图和语言。

  • 文件菜单有以下选项:

    • 新建:开始另一个新的文本窗口。

    • 保存:保存或更新当前文本文件到笔记本区域。

    • 重命名:更改文件的名字(不太可能,因为你通常会保留untitledn这样的名字)。

    • 下载:同样,如果笔记本运行在网络上,这个选项更为有意义。如同上传的解释,桌面版安装中的下载可以让你将文件复制到计算机的其他部分。

  • 编辑菜单包含以下选项:

    • 查找:搜索字符串。

    • 查找与替换:搜索并替换字符串。

    • 分隔符:此行下方是调整当前使用的文本编辑器。

    • 键盘映射:为你的键盘设置自定义的功能映射。

    • 默认:已选中,因为它是默认选择。这意味着使用默认的文本编辑器。

    • Sublime Text:如果你更倾向于使用 Sublime 编辑器。

    • Vim:如果你更倾向于使用 Vim 编辑器。

    • emacs:如果你更倾向于使用 emacs 编辑器。

  • 视图菜单仅提供一个切换行号的选项。我想未来版本的包可能会有更多功能。类似地,对于其他文件类型,菜单可能会有所不同。

  • 语言菜单允许你指定此文本文件是否为特定类型的编程文件。这可以启用语法高亮,这是源代码编辑器的一个主要功能。该列表内容丰富:

新建文件夹

文件夹选项会创建一个命名为untitled的文件夹。

新 Python 3

新的 Python 3 选项会创建一个新的 Python 3 笔记本。你将看到一个新的浏览器面板,命名方式与此类似,如下截图所示。

这是一种非常不同的展示方式,其中要求在页面的单元格中输入 Python 代码,并在每个单元格内显示结果。

菜单非常丰富,包含文件、编辑、视图、插入、单元格、内核和帮助选项。我们拥有一个相当完整的集成开发环境IDE),用于创建 Python 编程:

文件菜单包含以下选项:

  • 新建笔记本:启动一个新的笔记本(另一个浏览器面板)

  • 打开...:从笔记本文件视图中选择一个文件打开

  • 另存为副本...:将当前笔记本完整复制到另一个浏览器面板

  • 重命名...:重命名当前笔记本

  • 保存并检查点:保存当前笔记本并记录检查点

检查点是保存笔记本所有信息的时间点。你可以有多个检查点,并随时将笔记本的状态恢复到之前的检查点状态。这是一个非常好的方式,让你在不担心丢失已完成工作的情况下,尝试新的分析思路。

  • 恢复到检查点:将你的笔记本恢复到先前的检查点

  • 打印预览:呈现笔记本打印格式的预览

  • 另存为:将笔记本以多种格式下载:

    • IPython 笔记本(其当前形式)

    • IPython

    • HTML 表示

    • Markdown 是一种专门的显示格式

    • REST – 重构文本,是一种易读的纯文本标记语言

    • PDF

    • 演示

  • 关闭并停止:关闭当前笔记本并停止所有正在运行的脚本

你笔记本中的每个矩形工作区都是一个单元格。最内层的文本区域是你输入代码的地方。下方(但仍在矩形框内),将显示每次代码运行的结果。

  • 编辑菜单包含以下选项:

    • 复制单元格:将剪贴板中的单元格复制到当前光标位置。

    • 粘贴单元格到上方:将剪贴板中的单元格粘贴到当前单元格上方。

    • 粘贴单元格到下方:将剪贴板中的单元格粘贴到当前单元格下方。

    • 粘贴单元格并替换:将剪贴板中的单元格粘贴到当前单元格上方并替换它。

    • 删除单元格:删除当前单元格。

    • 撤销删除单元格:撤销上次删除单元格的操作。

    • 拆分单元格:从当前光标位置拆分单元格。

    • 合并上方单元格:将当前单元格与上方的单元格合并。

    • 合并下方单元格:将当前单元格与下方的单元格合并。

    • 编辑笔记本元数据:每个笔记本都有底层元数据,描述了笔记本的特性。高级用户可以直接操作这些数据,以便更容易地调整功能。例如,当前的笔记本元数据如下图所示:

  • 查找和替换:允许在选定的单元格中进行查找和替换。此功能有一个标准化的对话框,如下图所示:

  • 如前所示,参数及其功能如下:

    • Aa 图标切换决定是否进行大小写不敏感的搜索

    • 图标切换决定是否进行正则表达式搜索

    • 堆叠线条图标切换决定是否进行替换

    • 查找文本框用于输入搜索条件

    • 替换文本框用于输入替换文本

  • 视图菜单包含以下选项:

    • 切换标题:切换 Jupyter 徽标和文件名的显示

    • 切换工具栏:切换工具栏的显示

    • 单元格工具栏:切换单元格操作图标的显示

  • 插入菜单包含以下选项:

    • 在上方插入单元格:在当前单元格上方添加一个新单元格

    • 在下方插入单元格:在当前单元格下方添加一个新单元格

  • 单元格菜单包含以下选项:

    • 运行单元格:运行选定的(或所有)单元格。

    • 运行单元格并选择下方:运行当前单元格及其以下的单元格,并在下方创建一个新单元格。

    • 运行单元格并插入下方:运行当前单元格并在上方创建一个新单元格。

    • 运行所有:运行所有单元格。

    • 运行所有上方单元格:运行当前单元格之前的所有单元格。

    • 运行所有下方单元格:运行当前单元格以下的所有单元格。

    • 单元格类型:将选定单元格的类型更改为代码、Markdown 或 NBConvert。会显示一个自动消息,说明所有单元格默认是代码类型。

    • 当前输出和所有输出具有切换显示的选项。

  • 内核菜单包含以下选项:

    • 中断:向内核发送键盘中断,Ctrl + C。如果你的代码进入了无限循环,这非常有用。

    • 重启:重启内核。

    • 重启并清除输出:重新启动内核并清除所有输出。

    • 重启并运行所有:重启内核并运行所有单元格。

    • 重新连接:重新连接到远程笔记本。

    • 更换内核:目前只有 Python 2 可用,因此此选项不适用。

  • 帮助菜单有以下选项:

    • 用户界面教程:带领用户完成界面巡览

    • 键盘快捷键:展示内置的键盘快捷键列表

    • 笔记本帮助:展示关于笔记本的帮助主题

    • Markdown:描述笔记本中可用的 markdown

    • Python 参考文献,IPython 参考文献,NumPy 参考文献,SciPy 参考文献,Matplotlib 参考文献,SymPy 参考文献,Pandas 参考文献:关于笔记本中可使用的各种语言和包的帮助主题

    • 关于:标准的关于框

菜单下方有一个图标面板,包含前述功能的快捷图标:

  • 软盘图标:保存并创建检查点。

  • 加号:在下方插入单元格。

  • 剪刀:剪切选中的单元格。

  • 重复页面:复制选中的单元格。

  • 向上箭头:将选中的单元格上移。

  • 向下箭头:将选中的单元格下移。

  • 像扬声器的图标:运行当前单元格。

  • 黑色方块:中断内核。

  • 圆形箭头:重启内核(带对话框)。

  • 用于显示特性的下拉菜单:

    • 代码

    • Markdown

    • 原始 NBConvert

    • 标题

  • 键盘:打开命令面板。

  • 更改当前使用的工具栏。点击单元格工具栏按钮会自动显示视图菜单中的单元格工具栏选项。

Jupyter 的安全性

Jupyter 笔记本是为了与其他用户共享而创建的,通常是通过互联网进行分享。然而,Jupyter 笔记本能够执行任意代码并生成任意代码。如果笔记本中包含恶意部分,这可能会成为问题。Jupyter 笔记本的默认安全机制包括以下内容:

  • 原始 HTML 总是经过清理(检查是否有恶意代码)。更多信息请访问 developers.google.com/caja

  • 你无法运行外部 JavaScript。

  • 单元格内容(特别是 HTML 和 JavaScript)不被信任(需要用户验证才能继续)。

  • 任何单元格的输出都不被信任。

  • 所有其他 HTML 或 JavaScript 永不被信任,且清除输出后,保存时笔记本会变为信任状态。

安全摘要

笔记本还可以使用安全摘要,确保正确的用户正在修改内容。摘要会考虑整个笔记本内容和一个密钥(仅笔记本创建者知道)。这种组合确保恶意代码不会被添加到笔记本中。

你可以通过以下命令为笔记本添加安全摘要:

~/.jupyter/profile_default/security/notebook_secret 

在此,你需要将 notebook_secret 部分替换为你的密钥。

信任选项

你可以通过使用以下命令行选项,特定地将信任应用到笔记本上:

jupyter trust /path/to/notebook.ipynb 

或者,你可以在打开笔记本后,通过“文件 | 信任笔记本”菜单选项进行设置。

Jupyter 配置选项

你可以配置一些在展示 Notebook 时使用的显示参数。这些参数是可配置的,原因是 Notebook 使用了一个产品(CodeMirror)来展示和修改内容。CodeMirror 是一个基于 JavaScript 的编辑器,用于网页(Notebook)中。

可配置选项的列表仍在开发中。以下是一些选项:

  • 行分隔符:用于分隔文本行的字符

  • 主题:Notebook 中使用的整体展示主题

  • 缩进单位:用于缩进代码块的空格数

若要更改某个选项的配置,你可以打开浏览器的 JavaScript 窗口,输入修改选项的代码,然后加载你的 Notebook。接下来,你所做的修改将会应用到 Notebook 的展示中。关于这一点的进一步文档可以参考 codemirror.net/doc/manual.html#option_indentUnit.

例如,要更改 Notebook 的缩进(缩进单位),你可以使用以下 JavaScript:

var mycell = Jupyter.notebook.get_selected_cell(); 
var cell_config = mycell.config; 
var code_patch = { 
      CodeCell:{ 
        cm_config:{indentUnit:2} 
      } 
    } 
cell_config.update(code_patch) 

你现在已经看到了在 Jupyter Notebook 中可以使用的所有标准操作。

摘要

在本章中,我们研究了 Notebook 中可用的各种用户界面元素。我们学习了如何在 macOS 或 Microsoft PC 上安装软件。我们了解了 Notebook 的结构。我们看到了开发 Notebook 时使用的典型工作流程,并走访了 Notebook 中可用的用户界面操作。最后,我们了解了一些高级用户可以用来配置 Notebook 的选项。

在下一章中,我们将学习关于 Jupyter Notebook 中 Python 脚本编写的所有内容。

第二章:Jupyter Python 脚本编写

Jupyter 最初是 IPython,一个交互式版本的 Python,用作开发环境。因此,当你在开发 Notebook 时,大多数 Python 的特性都可以使用。

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

  • 基础 Python 脚本编写

  • Python 数据集访问(来自库)

  • Python pandas

  • Python 图形

  • Python 随机数

Jupyter 中的基础 Python

我们必须打开 Notebook 的 Python 部分才能使用 Python 代码。因此,启动你的 Notebook,然后在右上角的菜单中选择 Python 3:

这将打开一个用于工作的 Python 窗口:

正如上一章所提到的,新窗口显示了一个空单元格,方便你输入 Python 代码。

给新的工作区命名为Learning Jupyter 5, Chapter 2。自动保存应该已开启(如你在标题旁边所见)。通过准确的命名,我们可以轻松地从 Notebook 首页再次找到这一部分。如果你选择浏览器的主页标签并刷新它,你将看到这个新窗口名称显示出来:

注意,它有一个 Notebook 图标,而不是文件夹图标。自动分配的扩展名是 .ipynb(Python Notebook)。并且,由于该项处于 Jupyter 环境中的浏览器中,因此它被标记为正在运行。你的磁盘目录中也有一个相同名称的文件:

如果你在文本编辑器中打开 .ipynb 文件,你会看到 Jupyter 节点的基本内容(如在第一章的 Notebook 结构 部分中提到的,Jupyter 入门)。我们有一个空的单元格和关于 Notebook 的元数据:

{ 
 "cells": [ 
 { 
   "cell_type": "code", 
   "execution_count": null, 
   "metadata": {}, 
   "outputs": [], 
   "source": [] 
  } 
 ], 
 "metadata": { 
  "kernelspec": { 
   "display_name": "Python 3", 
   "language": "python", 
   "name": "python3" 
  }, 
  "language_info": { 
   "codemirror_mode": { 
    "name": "ipython", 
    "version": 3 
   }, 
   "file_extension": ".py", 
   "mimetype": "text/x-python", 
   "name": "python", 
   "nbconvert_exporter": "python", 
   "pygments_lexer": "ipython3", 
   "version": "3.6.4" 
  } 
 }, 
 "nbformat": 4, 
 "nbformat_minor": 2 
} 

现在我们可以在单元格中输入 Python 代码。例如:

  • 在第一个单元格中输入一些 Python 代码

  • 在末尾添加一个新的单元格(使用“插入单元格上方”或“插入单元格下方”菜单命令):

   name = "Dan" 
   age = 37 
  • 在第二个单元格中,输入引用第一个单元格中变量的 Python 代码:
print(name + ' is ' + str(age) + ' years old.') 
  • 然后我们将看到这个显示:

需要注意的是,Jupyter 会为你的 Python 代码着色(就像一个合格的编辑器一样),并且每个代码块的左侧都有这些空的括号。

如果我们执行“运行全部”,结果会以内联的形式显示出来:

现在我们已经填充了括号,显示了单元格编号,单元格的输出被附加在每个单元格的底部。需要注意的是,第二个单元格能够引用在第一个单元格中声明的变量。

如果我们等待自动保存启动或点击保存图标(最左边的软盘图标),我们将更新磁盘上的 .pynb 文件,并保存我们的结果:

{ 
 "cells": [ 
  { 
   "cell_type": "code", 
   "execution_count": null, 
   "metadata": {}, 
   "outputs": [], 
   "source": [ 
    "name = \"Dan\"\n", 
    "age = 37" 
   ] 
  }, 
  { 
   "cell_type": "code", 
   "execution_count": null, 
   "metadata": {}, 
   "outputs": [], 
   "source": [ 
    "print(name + ' is ' + str(age) + ' years old.')" 
   ] 
  } 
 ], 
 "metadata... as above 

有趣的是,Jupyter 会跟踪最后一次生成的输出,并保存在文件的保存版本中。你也可以使用 Cell| All Output | Clear 命令来清除输出。

如果你重新运行单元格(使用 Cell | Run All),输出将被重新生成(并通过自动保存保存)。如果你这样做,单元格编号会递增——Jupyter 会跟踪每个单元格的最新版本。

同样,如果你关闭浏览器标签页,刷新主页标签页的显示,找到我们创建的新项目(Learning Jupyter 5, Chapter 2.pynb),并点击它,之前创建的新标签页将显示出来,展示我们上次运行时生成的输出。

如果你打开服务器命令行窗口(Jupyter 服务运行的地方),你会看到我们在会话中所做的操作列表:

日志条目处于较高的级别。如果遇到困难,可能可以增加日志级别。

Python 数据访问在 Jupyter 中

现在我们已经了解了 Python 在 Jupyter 中的工作方式,包括底层编码,那么 Python 如何在 Jupyter 中访问一个大型数据集呢?

我为 pandas 创建了另一个视图,使用 Python 数据访问作为名称。从这里开始,我们将读取一个大型数据集,并计算一些标准统计数据。我们感兴趣的是了解如何在 Jupyter 中使用 pandas,脚本的性能如何,以及元数据中存储了哪些信息(尤其是当数据集较大时)。

我们的脚本访问了一个内建于 Python 包中的iris数据集。我们要做的仅仅是读取略多数量的项目,并对数据集进行一些基本操作。我们真正感兴趣的是看到有多少数据被缓存到了.pynb文件中。

Python 代码如下:

# import the datasets package 
from sklearn import datasets 

# pull in the iris data 
iris_dataset = datasets.load_iris() 
# grab the first two columns of data 
X = iris_dataset.data[:, :2] 

# calculate some basic statistics 
x_count = len(X.flat) 
x_min = X[:, 0].min() - .5 
x_max = X[:, 0].max() + .5 
x_mean = X[:, 0].mean() 

# display our results 
x_count, x_min, x_max, x_mean 

我将这些步骤分成了几个 Jupyter 单元格:

现在,运行单元格(使用 Cell | Run All),我们将看到以下显示。唯一的区别是最后一行输出,我们的值将在那里显示:

看起来加载库的时间(我第一次运行脚本时)比读取数据和计算统计数据的时间还要长。

如果我们查看这个 Notebook 的.pynb文件,我们会发现没有数据被缓存到.pynb文件中。我们只是有对库的代码引用、我们的代码和上次计算脚本时的输出:

{ 
 "cells": [ 
  { 
   "cell_type": "code", 
   "execution_count": 2, 
   "metadata": {}, 
   "outputs": [], 
   "source": [ 
    "# import the datasets package\n", 
    "from sklearn import datasets" 
   ] 
  }, 
  { 
   "cell_type": "code", 
   "execution_count": 3, 
   "metadata": {}, 
   "outputs": [], 
   "source": [ 
    "# pull in the iris data\n", 
    "iris_dataset = datasets.load_iris()\n", 
    "# grab the first two columns of data\n", 
    "X = iris_dataset.data[:, :2]" 
   ] 
  }, 
  { 
   "cell_type": "code", 
   "execution_count": 4, 
   "metadata": {}, 
   "outputs": [], 
   "source": [ 
    "# calculate some basic statistics\n", 
    "x_count = len(X.flat)\n", 
    "x_min = X[:, 0].min() - .5\n", 
    "x_max = X[:, 0].max() + .5\n", 
    "x_mean = X[:, 0].mean()" 
   ] 
  }, 
  { 
   "cell_type": "code", 
   "execution_count": 5, 
   "metadata": {}, 
   "outputs": [ 
    { 
     "data": { 
      "text/plain": [ 
       "(300, 3.8, 8.4, 5.843333333333334)" 
      ] 
     }, 
     "execution_count": 5, 
     "metadata": {}, 
     "output_type": "execute_result" 
    } 
   ], 
   "source": [ 
    "# display our results\n", 
    "x_count, x_min, x_max, x_mean" 
   ] 
  } 
 ]... 

Python pandas 在 Jupyter 中

Python 最广泛使用的功能之一是 pandas。pandas 是内建的数据分析库,可以自由使用。在这个示例中,我们将开发一个 Python 脚本,利用 pandas 来查看它们在 Jupyter 中的使用效果。

我使用的是www.kaggle.com/c/titanic/data上的 Titanic 数据集。我确信其他来源也有相同的数据。

请注意,你必须注册Kaggle才能下载数据,且注册是免费的。

这是我们希望在 Jupyter 中运行的 Python 脚本:

from pandas import * 
training_set = read_csv('train.csv') 
training_set.head() 
male = training_set[training_set.Sex == 'male'] 
female = training_set[training_set.Sex =='female'] 
womens_survival_rate = float(sum(female.Survived))/len(female) 
mens_survival_rate = float(sum(male.Survived))/len(male) 
womens_survival_rate, mens_survival_rate

结果是我们根据性别计算了乘客的生存率。

我们创建一个新的 Notebook,将脚本输入到适当的单元格中,在每个步骤中添加计算数据的显示,并生成我们的结果。

这是我们布局的 Notebook,在每个单元格中都添加了计算数据的显示:

当我运行这个脚本时,我遇到了两个问题:

在 Windows 上,通常使用反斜杠(\)来分隔文件名的各部分。然而,这种编码将反斜杠用作特殊字符。因此,我不得不将.csv文件路径中的反斜杠改为正斜杠(/)。

数据集的列名直接来自文件,并且区分大小写。在这种情况下,我最初在脚本中使用了sex字段,但在.csv文件中,列名是Sex。类似地,我不得不将survived改为Survived

当我们运行脚本时,最终的脚本和结果如下所示:

我使用了head()函数来显示数据集的前几行。很有趣的是,所有乘客的详细信息都能显示出来。

如果你向下滚动,你将看到结果:

我们可以看到,74%的幸存者是女性,而只有 19%是男性。我希望绅士风度没有消失。

很奇怪,结果总和并不等于 100%。然而,就像我见过的其他数据集一样,这里也存在缺失和/或不准确的数据。

Jupyter 中的 Python 图形

Python 图形如何在 Jupyter 中工作?

我为此开始了另一个视图,命名为 Python 图形(s),以便区分这部分工作。

如果我们构建一个包含婴儿名字及该名字每年出生数量的样本数据集,我们就可以绘制出数据图表。

Python 编码很简单:

import pandas 
import matplotlib 

%matplotlib inline 

# define our two columns of data 
baby_name = ['Alice','Charles','Diane','Edward'] 
number_births = [96, 155, 66, 272] 

# create a dataset from the to sets 
dataset = list(zip(baby_name,number_births)) 
dataset 

# create a Python dataframe from the dataset 
df = pandas.DataFrame(data = dataset, columns=['Name', 'Number']) 
df 

# plot the data 
df['Number'].plot() 

脚本的步骤如下:

  1. 导入我们需要的图形库(和数据库)

  2. 定义我们的数据

  3. 将数据转换为一种便于图形显示的格式

  4. 绘制数据

我们可以预期得到一个按婴儿名字分布的出生数量图表。

将之前的脚本放入 Jupyter 单元格后,我们得到如下结果:

  • 我将脚本拆分成不同的单元格,以提高可读性。拥有不同的单元格还允许你一步步地开发脚本,在每个步骤展示计算的值,以验证结果。我在大多数单元格中通过显示数据集和数据框来实现这一点。

  • 当我们运行这个脚本(Cell | Run All)时,我们可以看到每一步的结果随着脚本的执行逐步显示:

  • 最终,我们可以看到我们的出生数据图:

  • 我对这个脚本存储了什么元数据感到好奇。通过查看 .ipynb 文件,你可以看到公式单元格的期望值。

  • 数据框的表格数据以 HTML 格式方便地存储:

...{ 
   "cell_type": "code", 
   "execution_count": 4, 
   "metadata": {}, 
   "outputs": [ 
    { 
     "data": { 
      "text/html": [ 
       "<div>\n", 
       "<style scoped>\n", 
       "    .dataframe tbody tr th:only-of-type {\n", 
       "        vertical-align: middle;\n", 
       "    }\n", 
       "\n", 
       "    .dataframe tbody tr th {\n", 
       "        vertical-align: top;\n", 
       "    }\n", 
       "\n", 
       "    .dataframe thead th {\n", 
       "        text-align: right;\n", 
       "    }\n", 
       "</style>\n", 
       "<table border=\"1\" class=\"dataframe\">\n", 
       "  <thead>\n", 
       "    <tr style=\"text-align: right;\">\n", 
       "      <th></th>\n", 
       "      <th>Name</th>\n", 
       "      <th>Number</th>\n", 
       "    </tr>\n",... 
  • 图形输出单元格存储如下:
  { 
     { 
   "cell_type": "code", 
   "execution_count": 5, 
   "metadata": {}, 
   "outputs": [ 
    { 
     "data": { 
      "text/plain": [ 
       "<matplotlib.axes._subplots.AxesSubplot at 0x1822deb44a8>" 
      ] 
     }, 
     "execution_count": 5, 
     "metadata": {}, 
     "output_type": "execute_result" 
    }, 
    { 
     "data": { 
      "image/png": "iVBORw0... 
"<a hundred lines of hexcodes> 
...VTRitYII=\n", 
      "text/plain": [ 
       "<matplotlib.figure.Figure at 0x1822e26a828>" 
      ] 
     },... 

其中 image/png 标签包含一个大型十六进制数字字符串表示屏幕上显示的图形图像(我在显示的代码中简化了这个显示)。因此,实际生成的图像存储在页面的元数据中。

所以,Jupyter 不是缓存,而是记住了每个单元格最后执行时的输出。

Jupyter 中的 Python 随机数

对于许多分析,我们需要计算可重复的结果。然而,很多分析依赖于使用随机数。在 Python 中,你可以通过 random.seed() 函数设置 seed 来为随机数生成器实现可重复的结果。

在这个例子中,我们模拟掷一对骰子并观察结果。我们预计两个骰子的总和的平均值是六,这是两个面之间的中点。

我们使用的脚本如下:

# using pylab statistics and histogram
import pylab
import random

# set random seed so we can reproduce results
random.seed(113)
samples = 1000

# declare our dataset store
dice = []

# generate and save the samples
for i in range(samples):
    total = random.randint(1,6) + random.randint(1,6)
    dice.append(total)

# compute some statistics on the dice throws
print("Throw two dice", samples, "times.")
print("Mean of", pylab.mean(dice))
print("Median of", pylab.median(dice))
print("Std Dev of", pylab.std(dice))

# display a histogram of the results
pylab.hist(dice, bins= pylab.arange(1.5,12.6,1.0))
pylab.xlabel("Pips")
pylab.ylabel("Count")
pylab.show()

一旦我们在 Jupyter 中执行脚本,我们将得到以下结果:

我增加了一些统计数据。我不确定我是否会依赖如此高的标准差。如果我们增加 samples 的数量,这个标准差会减少。

结果图形被打开在一个新窗口中,就像你在另一个 Python 开发环境中运行这个脚本时那样:

图形看起来比我预期的略显锯齿状,尤其是在一千个样本的情况下。

总结

在这一章中,我们介绍了一个简单的 Notebook 及其基础结构。然后,我们看了一个使用 pandas 的示例,并查看了图形示例。最后,我们看了一个使用 Python 脚本生成随机数的示例。

在下一章中,我们将学习在 Jupyter Notebook 中使用 R 脚本。

第三章:Jupyter R 脚本

Jupyter 的本地语言是 Python。自从 Jupyter(在改名之前本质上是 IPython)因数据分析而变得流行后,许多人对在 Jupyter Notebook 中使用 R 编程分析工具套件产生了兴趣。

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

  • 将 R 脚本添加到你的安装中

  • 基本 R 脚本

  • R 数据集访问(来自一个库)

  • R 图形

  • R 聚类分析

  • R 预测

  • R 预测

将 R 脚本添加到你的安装中

两大安装平台是 macOS 和 Windows。针对这两个平台,添加 R 脚本到 Jupyter 的步骤是分开但相似的。

将 R 脚本添加到 macOS 上的 Jupyter

如果你在 macOS 上操作,你可以使用以下命令添加 R 脚本:

conda install -c r r-essentials 

这将开始大规模安装 R 环境,其中包含许多常用的包:

bos-mpdc7:~ dtoomey$ conda install -c r r-essentials 
Fetching package metadata: ...... 
Solving package specifications: ......... 

Package plan for installation in environment /Users/dtoomey/miniconda3: 

将下载以下包:

 package                    |            build 
 ---------------------------|----------------- 
 jbig-2.1                   |                0          31 KB 
 jpeg-8d                    |                2         210 KB 
 libgcc-4.8.5               |                1         785 KB 
... <<many packages>> 
 r-caret-6.0_62             |        r3.2.2_0a         4.3 MB 
 r-essentials-1.1           |        r3.2.2_1a          726 B 
 ------------------------------------------------------------ 
 Total:       101.0 MB 

将安装以下新包:

 jbig:           2.1-0 
 jpeg:           8d-2 
... <<many packages>> 
 r-xts:          0.9_7-r3.2.2_0a 
 r-yaml:         2.1.13-r3.2.2_1a 
 r-zoo:          1.7_12-r3.2.2_1a 
 zeromq:         4.1.3-0 

Proceed ([y]/n)? y 

Fetching packages ... 
jbig-2.1-0.tar 100% |################################| Time: 0:00:00   1.59 MB/s 
jpeg-8d-2.tar. 100% |################################| Time: 0:00:00   2.69 MB/s 
... <<many packages>> 
r-caret-6.0_62 100% |################################| Time: 0:00:00  11.16 MB/s 
r-essentials-1 100% |################################| Time: 0:00:00 537.43 kB/s 
Extracting packages ... 
[      COMPLETE      ]|###################################################| 100% 
Linking packages ... 
[      COMPLETE      ]|###################################################| 100% 

从那里,你可以像往常一样调用你的notebook

ipython notebook 

将 R 脚本添加到 Windows 上的 Jupyter

Anaconda 的默认安装不包含 R——我不确定为什么。安装 Anaconda 后,你需要使用命令行特别安装它。然后,我们将采取措施,添加 R 脚本,这与我们为 macOS 所做的非常相似:

conda install -c r notebook r-irkernel 

这会生成一个更新的包的详细视图。在我的例子中,它安装了完整的 R 包和运行时,尽管我之前在机器上的其他地方使用过 R:

C:\Users\Dan>conda install -c r notebook r-irkernel
Solving environment: done
==> WARNING: A newer version of conda exists. <==
current version: 4.4.10
latest version: 4.5.4
Please update conda by running
$ conda update -n base conda
## Package Plan ##
environment location: C:\Users\Dan\Anaconda3
added / updated specs:
- notebook
- r-irkernel The following packages will be downloaded:
package | build
-------------------|-----------------r-pbdzmq-0.2_6 | mro343h889e2dd_0 4.2 MB r
libxml2-2.9.8 | vc14_0 3.2 MB conda-forge
...
r-stringr-1.2.0 | mro343h889e2dd_0 143 KB r
r-repr-0.12.0 | mro343h889e2dd_0 68 KB r
r-irdisplay-0.4.4 | mro343h889e2dd_0 29 KB r
jpeg-9b | vc14_2 314 KB conda-forge
r-r6-2.2.2 | mro343_0 5.4 MB r
r-digest-0.6.13 | mro343h889e2dd_0 168 KB r
------------------------------------------------------------
Total: 140.2 MB
The following NEW packages will be INSTALLED:
mro-base: 3.4.3-0 r
r-crayon: 1.3.4-mro343h889e2dd_0 r
r-digest: 0.6.13-mro343h889e2dd_0 r
r-evaluate: 0.10.1-mro343h889e2dd_0 r
...
r-stringr: 1.2.0-mro343h889e2dd_0 r
r-uuid: 0.1_2-mro343h889e2dd_0 r
The following packages will be UPDATED:
_r-mutex: 1.0.0-anacondar_1 r --> 1.0.0-mro_2 r
jpeg: 9b-hb83a4c4_2 --> 9b-vc14_2 conda-forge [vc14]
libxml2: 2.9.7-h79bbb47_0 --> 2.9.8-vc14_0 conda-forge [vc14]
sqlite: 3.22.0-h9d3ae62_0 --> 3.22.0-vc14_0 conda-forge [vc14] r-base: 3.4.3-h6bb4b03_0 r --> 3.4.1-1 conda-forge
Proceed ([y]/n)? y
Downloading and Extracting Packages
r-pbdzmq 0.2_6: ########################################## | 100%
libxml2 2.9.8: ########################################### | 100%
r-irkernel 0.8.11: ####################################### | 100%
r-magrittr 1.5: ########################################## | 100%
r-evaluate 0.10.1: ####################################### | 100%
_r-mutex 1.0.0: ########################################## | 100%
...
r-digest 0.6.13: ########################################################## | 100%
Preparing transaction: done
Verifying transaction: done
Executing transaction: done

现在,当你启动 Jupyter 并下拉内核菜单时,你将看到 R 作为一个选项:

将 R 包添加到 Jupyter

Jupyter 下的标准 R 安装包含许多在 R 编程中常用的包。但是,如果你确实需要添加另一个包,需要按照少数几个步骤进行操作:

  1. 关闭你的 Notebook(包括服务器)。

  2. 在命令行窗口中,输入以下内容:

R install.packages("name of the R package you want to add") 
quit() 
# answer Yes to save 
  1. 重新启动你的 Notebook,包应该可以在你的 R 脚本中使用,例如,library (name of the R package you want to add)

请注意,你可能仍然会遇到 R 的问题,即你安装的 R 的核心版本已经过时,因此你需要将其升级以使用特定的库。

Jupyter 中的 R 限制

在本章中,我们使用了多种包,包括预安装的和专门为示例安装的。我使用了 Jupyter 下可用的各种 R 材料,并且没有发现任何限制;你可以在 Jupyter 中执行大部分标准 R 实现中能做到的步骤。唯一的限制是,当你使用Shiny或者试图使用大量 Markdown 时:

  • 对于 Shiny,我认为你混淆了目的——Jupyter 和 Shiny 都提供 Web 体验——所以我不确定如何决定它是否应该工作。这个问题正在由 Jupyter 开发组处理。

  • 使用大量 Markdown 似乎也不是一个好主意。Markdown 的目的是让 Notebook 开发者能够以更具表现力的方式增强标准的 R 输出。我认为,如果你在 Notebook 中加入了大量的 Markdown,你真的应该开发一个网站——也许使用 Shiny,然后你就能使用所有的 HTML Markdown 了。

在 Jupyter 中使用基础 R

启动一个新的 R Notebook 并将其命名为 R Basics。我们可以输入一个小脚本,以便查看 R 脚本步骤的进展。将以下内容分别输入到 Notebook 的不同单元格中:

myString <- "Hello, World!" 
print (myString) 

从这里,你将得到一个看起来像这样的启动屏幕:

我们应该注意 R Notebook 视图的几个方面:

  • 我们在右上角看到 R 标志。你将在其他 R 安装中看到这个标志。

  • 下面的 R 图标下方还有一个特殊的 R O。如果 O 是未填充的圆圈,表示内核处于空闲状态;而填充的圆圈表示内核正在工作。

  • 剩下的菜单项与我们之前看到的相同。

这是一个非常简单的脚本——在一个单元格中设置一个变量,然后在另一个单元格中打印出它的值。一旦执行(单元格 | 运行所有),你将看到你的结果:

所以,就像在 R 解释器中运行脚本一样,你得到了输出(带有数字前缀)。Jupyter 已经对语句进行了编号,以便我们可以看到单元格的递增编号。Jupyter 并没有做任何特殊的事情来打印变量以供调试;你需要单独做这件事。

如果我们查看 R 服务器的日志语句(在启动 Jupyter 时创建了一个命令行窗口),我们将能够看到发生的操作:

$ jupyter notebook 
[I 11:00:06.965 NotebookApp] Serving notebooks from local directory: /Users/dtoomey/miniconda3/bin 
[I 11:00:06.965 NotebookApp] 0 active kernels  
[I 11:00:06.965 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/ 
[I 11:00:06.965 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). 
[I 11:00:17.447 NotebookApp] Creating new notebook in  
[I 11:00:18.199 NotebookApp] Kernel started: 518308da-460a-4eb9-9959-1411e31dec69 
[1] "Got unhandled msg_type:" "comm_open"               
[I 11:02:18.160 NotebookApp] Saving file at /Untitled.ipynb 
[I 11:08:27.340 NotebookApp] Saving file at /R Basics.ipynb 
[1] "Got unhandled msg_type:" "comm_open"               
[I 11:14:45.204 NotebookApp] Saving file at /R Basics.ipynb 

我们启动了服务器,创建了一个新的 Notebook,并将其保存为 R Basics。如果我们在磁盘上打开该 IPYNB 文件(使用文本编辑器),我们将能够看到以下内容:

{ 
  "cells": [ 
    ...<similar to previously displayed> 
  ], 
  "metadata": { 
    "kernelspec": { 
      "display_name": "R", 
      "language": "R", 
      "name": "ir" 
    }, 
    "language_info": { 
      "codemirror_mode": "r", 
      "file_extension": ".r", 
      "mimetype": "text/x-r-source", 
      "name": "R", 
      "pygments_lexer": "r", 
      "version": "3.4.3" 
    } 
  }, 
  ...<omitted> 
} 

这与我们在前一章关于 Python Notebook 编程中看到的有所不同。特别地,元数据明确指示脚本单元格为 R 脚本。请注意,实际的单元格并不特定于某一种语言——它们只是按照元数据指令执行的脚本。

R 数据集访问

在这个例子中,我们将使用 Iris 数据集。Iris 是 R 安装包中的内置数据集,可以直接使用。我们只需导入数据,收集一些简单的统计数据,并绘制数据图。这将展示 R 在 Jupyter 中访问数据集,使用 R 内置包,以及一些可用的统计数据(因为我们有 R),并与 R 图形进行交互。

我们将使用的脚本如下:

data(iris) 
summary(iris) 
plot(iris) 

如果我们将这段小脚本输入到一个新的 R 笔记本中,我们将得到一个初始显示,类似如下所示:

我预计输出会是标准的 R 统计摘要,并且我知道鸢尾花的图形非常有趣。我们可以在以下截图中清楚地看到发生了什么:

图形在以下截图中继续显示,因为它无法容纳在单一页面内:

Jupyter 的一个特点是将较大的图形,例如这个,放置到一个视口中,只显示图像的一部分。我能够将图像从视口窗口中完全拖出并拍摄这张照片。你可以通过点击视口并拖动它,消除视口边界并显示整个输出。

Jupyter 中的 R 可视化

R 的一个常见用途是使用多种可视化,这些可视化会根据底层数据而有所不同。在本节中,我们将介绍其中的一些,看看 R 如何与 Jupyter 互动。

Jupyter 中的 R 3D 图形

一个可用于 3D 图形的包是 persppersp 包绘制在二维空间上的透视图。

我们可以通过在新的笔记本中输入基本的 persp 命令,只需使用以下命令:

example(persp) 

所以,我们将在笔记本中看到类似的内容:

一旦我们运行步骤(Cell | Run All),我们将看到如下截图所示的显示。第一部分是生成图形的脚本(这是示例代码的一部分):

然后,我们将看到以下图形显示:

Jupyter 中的 R 3D 散点图

R 的 lattice 包含一个 Cloud 函数,可以生成 3D 散点图。

我们将使用的脚本如下:

# make sure lattice package is installed 
install.packages("lattice") 

# in a standalone R script you would have a command to download the lattice library - this is not needed in Jupyter 

library("lattice") 
# use the automobile data from ics.edu 
mydata <- read.table("http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data") 

# define more meaningful column names for the display 
colnames(mydata) <- c("mpg", "cylinders", "displacement", "horsepower", "weight", "acceleration", "model.year", "origin", "car.name") 

# 3-D plot with number of cylinders on x axis, weight of the vehicle on the y axis and miles per gallon on the z axis. 
cloud(mpg~cylinders*weight, data=mydata) 

在运行之前,我们将看到如下内容:

请注意,我们使用了标记类型单元格来注释脚本步骤。它们也没有在左侧列中标明脚本行号。

如果你将 R 脚本复制到 Jupyter 窗口中,可能会遇到一个问题,就是你使用的打印复制中包含非标准的双引号字符(左侧的引号倾向于左侧,而右侧的引号倾向于右侧)。一旦复制到 Jupyter 中,你需要将其更改为正常的双引号(它们不会倾斜,而是垂直的)。

运行此后,我们将看到以下显示:

R 聚类分析

在这个例子中,我们将使用 R 的聚类分析函数来确定来自 uci.edu/ 的小麦数据集的聚类。

我们想在 Jupyter 中使用的 R 脚本如下:

# load the wheat data set from uci.edu 
wheat <- read.csv("http://archive.ics.uci.edu/ml/machine-learning-databases/00236/seeds_dataset.txt", sep="\t") 

# define useful column names 
colnames(wheat) <-c("area", "perimeter", "compactness", "length", "width", "asymmetry", "groove", "undefined") 

# exclude incomplete cases from the data 
wheat <- wheat[complete.cases(wheat),] 

# calculate the clusters 
set.seed(117) #to make reproducible results 
fit <- kmeans(wheat, 5) 
fit 

一旦输入到笔记本中,我们将看到如下内容:

生成的聚类信息是使用 k-means 聚类,包含五个大小为 39、53、47、29 和 30 的聚类(注意,我设置了随机数的种子值,因此你的结果不会有所不同):

所以,我们生成了五个聚类的信息(传递到拟合语句中的参数)。聚类平方和变化很大,这有点麻烦。

R 预测

在本示例中,我们将预测 Fraser River 的水位,数据来源于 datamarket.com/data/set/22nm/fraser-river-at-hope-1913-1990#!ds=22nm&display=line。我未能找到合适的来源,因此我手动从该网站提取了数据并存储到本地文件中。

我们将使用 R 的 forecast 包。你需要将此包添加到你的设置中(如本章开始时所述)。

我们将使用的 R 脚本如下:

library(forecast) 
fraser <- scan("fraser.txt") 
plot(fraser) 
fraser.ts <- ts(fraser, frequency=12, start=c(1913,3)) 
fraser.stl = stl(fraser.ts, s.window="periodic") 
monthplot(fraser.stl) 
seasonplot(fraser.ts) 

本示例中感兴趣的输出是三个图表:简单图表、每月图表和计算的季节性图表。

当这被输入到笔记本中时,我们将得到一个熟悉的布局:

简单图表(使用 R 的 plot 命令)类似于以下截图所示。没有明显的组织或结构:

每月图表(使用 monthplot 命令)类似于以下截图所示。河流流量在一个月内似乎非常一致:

最后,seasonalplot 显示了我们试图预测的内容,即河流流量的明显季节性:

R 机器学习

在本节中,我们将使用一种机器学习方法,具体如下:

  • 将数据集划分为训练集和测试集

  • 生成数据的模型

  • 测试我们模型的效率

数据集

机器学习通过提供一个数据集来工作,我们将把它分成训练部分和测试部分。我们将使用训练数据来构建模型。然后,我们可以证明或测试该模型对测试数据集的适应性。

要使数据集可用,我们至少需要几百个观察值。我使用的是来自 uci.edu 的住房数据。让我们通过以下命令加载数据集:

housing <- read.table("http://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data") 

该网站记录了变量的名称如下:

变量 描述
CRIM 人均犯罪率
ZN 住宅区比例百分比
INDUS 城镇中非零售业务的比例
CHAS 到查尔斯河的距离(布尔值)
NOX 氮氧化物浓度
RM 每个住宅的平均房间数
AGE 1940 年前建造的住房比例
DIS 到就业中心的加权距离
RAD 高速公路可达性
税收 每 $10,000 的税率
B 1,000(Bk-0.63)² Bk 代表黑人群体的百分比
LSTAT 低收入人群百分比
MEDV 自住房屋的中位数价值(以千美元为单位)

所以,让我们应用这些方法来理解数据:

colnames(housing) <- c("CRIM","ZN","INDUS","CHAS","NOX","RM","AGE","DIS","RAD", "TAX", "PRATIO","B","LSTAT", "MDEV") 

现在,我们可以得到一个总结,以便更好地理解这些值:

summary(housing) 

执行时,这会显示如下界面:

所以,数据有些过时。与该地区当前的房价相比,中位数值非常低。

此外,统计数据并不完全符合政治正确——B 因子是黑人群体的衡量标准。

总的来说,我们有相当多的变量。哪些变量可能是我们模型的候选者呢?我发现最好的工具是将每个变量与其他每个变量做简单的回归图。我们可以使用以下命令来实现:

plot(housing) 

R 做了我们需要的操作,显示如下:

我们在寻找的是正相关或负相关,也就是说,我们在寻找一条大约 45 度的正线或负线。任何呈现杂乱无章、垂直或水平模式的都不会给我们提供有关数据的有用信息。

最好的相关性出现在 RAD(高速公路可达性)和 TAX(每千美元的税率)之间。幸运的是,大多数变量都显示出了良好的相关性。

由于房价是我们的衡量标准,让我们在划分数据之前,先按房价对数据进行排序,使用以下命令:

housing <- housing[order(housing$MDEV),] 

我们将使用 caret 包来划分数据,所以让我们先加载它:

install.packages("caret") 
library("caret") 

现在,我们可以划分数据:

# set the random seed so we can reproduce results 
set.seed(311) 

# take 3/4 of the data for training 
trainingIndices <- createDataPartition(housing$MDEV, p=0.75, list=FALSE) 

# split the data 
housingTraining <- housing[trainingIndices,] 
housingTesting <- housing[-trainingIndices,] 

# make sure the paritioning is working 
nrow(housingTraining) 
nrow(housingTesting) 
381 
125 

划分计数看起来是正确的。让我们创建我们的模型,看看结果:

linearModel <- lm(MDEV ~ CRIM + ZN + INDUS + CHAS + NOX + RM + AGE + DIS + RAD + TAX + PRATIO + B + LSTAT, data=housingTraining) 
summary(linearModel) 

这是前面代码的截图:

有趣的是,几个变量并没有产生太大影响。这些变量是AGETAXB

我们已经有了模型,现在可以进行预测:

predicted <- predict(linearModel,newdata=housingTesting) 
summary(predicted) 
Min. 1st Qu. Median Mean 3rd Qu. Max.  
0.1378 17.4939 21.9724 22.0420 25.6669 40.9981 

我认为这个总结并没有告诉我们太多信息。将这两个变量对比(包括abline函数)的图表要更加具有信息量:

plot(predicted, housingTesting$MDEV) 
abline(lm(predicted ~ housingTesting$MDEV)) 

这是前面代码的截图:

从视觉上来看,两个变量之间似乎有着极好的相关性。

我们做一些数学计算,看看我们有多接近真实值。平方和将为我们提供一个很好的衡量标准。我没有找到内建的方法,所以我加了我自己的:

sumOfSquares <- function(x) { 
    return(sum(x²)) 
} 

#make sure it works 
sumOfSquares(1:5) 
15 
Testing our model: 
diff <- predicted - housingTesting$MDEV 
sumOfSquares(diff) 
2487.85072318584 

这里,我们得到了差异的平方和,大约为 2,500。对于几百个观测值来说,这个数值相当显著。

总结

在本章中,我们增加了在我们的 Jupyter 笔记本中使用 R 脚本的能力。我们添加了一个不包含在标准 R 安装中的 R 库,并编写了一个 R 的 Hello World 脚本。然后,我们看到了 R 数据访问的内置库,以及一些自动生成的简单图形和统计信息。我们使用一个 R 脚本以几种不同的方式生成了 3D 图形。然后,我们进行了标准的聚类分析(我认为这是 R 的基本用法之一),并使用了其中一个可用的预测工具。我们还建立了一个预测模型并测试了其准确性。

在下一章中,我们将学习如何在 Jupyter 笔记本中使用 Julia 脚本。

第四章:Jupyter Julia 脚本

Julia 是一门专为高性能数值计算设计的编程语言。最重要的是,它与本书中介绍的其他脚本语言(R 和在某种程度上是 Python)不同,因为 Julia 是一门完整的语言,而不仅仅局限于数据处理。

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

  • 将 Julia 脚本添加到你的安装环境中

  • Jupyter 中的基础 Julia

  • Julia 在 Jupyter 中的限制

  • 标准 Julia 功能

  • Jupyter 中的 Julia 可视化

  • Julia Vega 绘图

  • Julia 并行处理

  • Julia 控制流程

  • Julia 正则表达式

  • Julia 单元测试

将 Julia 脚本添加到你的安装环境中

我们将在 macOS 和 Windows 上安装 Julia。这两个环境中的步骤非常相似,因为安装是基于 Anaconda 的。

将 Julia 脚本添加到 Jupyter

一旦 Julia 在你的机器上可用,启用 Jupyter 中的 Julia 就变得非常简单。

首先,我们需要在 Windows 机器上安装 Julia。访问 Julia 下载页面(julialang.org/downloads/),下载正确的版本,大多数环境下是 Julia 0.6.1,并使用标准默认设置进行安装。

你必须以管理员身份运行 Julia 安装程序。在下载文件后,打开 Downloads 文件夹,右键点击 Julia 可执行文件,并选择“以管理员身份运行”。

安装完成后,你应该验证一切是否正常工作。从程序列表中选择 Julia 并运行该程序,你应该会看到显示 Julia 的命令行,如下图所示:

当前版本的 Julia 不会自动包含可能使用的包的更新。为此,我们运行以下命令将 Julia 添加到 Jupyter:

Pkg.update() 

这将导致一系列包被更新或安装到你的机器上。你的显示屏应该会类似于以下截图:

Julia 使用字体颜色作为反馈。我在屏幕顶部输入了白色的文本 Pkg.update();成功执行的步骤显示为蓝色,可能存在的问题显示为红色。你必须等待安装完成。

这是一个相当复杂的过程,系统会检查哪些包需要更新和安装,逐一安装每个包,验证每个包是否成功,然后再继续执行更新,直到没有剩余的包需要更新。

最后一行应该是如下所示:

INFO: Package database updated 

到这个时候,你可以关闭 Julia 窗口(使用 quit() 命令)。

最后一步是打开你的笔记本(使用 jupyter notebook 命令),如果你打开右上角的 New 菜单,你应该会看到一个可用的 Julia 类型,如下图所示:

在 Jupyter 中添加 Julia 包

在 Jupyter 中的标准 Julia 安装包含了许多常用的 Julia 编程包。然而,如果你确实需要添加其他包,还是需要按照一些简单的步骤操作:

  1. 关闭你的 Notebook(包括服务器)。

  2. 运行 Julia 命令行程序:

Pkg.add("DataFrames") 
Pkg.add("RDatasets") 
quit(); 
  1. 重新启动你的笔记本。这个包应该可以在你的 Julia 脚本中使用,例如,library("你想添加的包的名称")

我建议你立即添加前面提到的两个包,因为它们是许多脚本所必需的。

第一次使用 Julia 中的包时,你会看到一行被浅红色高亮显示,表示 Julia 正在预编译,例如:INFO: Precompiling module Dataframes...

你可以直接在脚本中使用 Pkg.add(...) 函数,但这似乎不太合适。每次你运行脚本时,系统都会尝试验证你是否已安装指定的包,若没有,则会安装到你的环境中,甚至告诉你它是否过时。这些步骤并不应该属于你的脚本的一部分。

Jupyter 中的基础 Julia

在这个例子中,我们将使用 Iris 数据集进行一些标准分析。所以,启动一个新的 Julia Notebook,并将其命名为 Julia Iris。我们可以输入一个小脚本,查看 Julia 脚本的执行过程。

这个脚本使用了另一个用于绘图的包,叫做 Gadfly。你需要像我们在上一节中所做的那样,按照类似的步骤安装该包,然后再运行脚本。

将以下脚本输入到笔记本的不同单元格中:

using RDatasets
using DataFrames
using Gadfly
set_default_plot_size(5inch, 5inch/golden); plot(dataset("datasets","iris"), x="SepalWidth", y="SepalLength", color="Species") 

RDatasets 是一个包含了几个常用 R 数据集的库,比如 iris。这是一个简单的脚本——我们定义了要使用的库,设置 plot 区域的大小,并绘制出 iris 数据点(根据 Species 用不同颜色编码)。

所以,你最终会得到一个启动屏幕,类似下面的截图:

我使用了 Markdown 单元格来展示文本单元格。这些单元格作为处理的文档记录,并不会被引擎解析。

我们应该注意一下 Julia Notebook 视图的几个方面:

  • 我们在右上角看到了 Julia 的徽标(那三个彩色圆圈)。你可能已经在其他 Julia 安装中看到过这个徽标(就像我们之前运行 Julia 命令行时看到的那样)。

  • 在 Julia 徽标右侧的圆圈是一个忙碌指示器。当你的脚本启动时,表格标题会显示为忙碌,因为 Julia 正在启动。当脚本运行时,圆圈会变成黑色。当它没有运行时,圆圈是空的。

  • 剩下的菜单项和以前一样。

在我的 Windows 机器上,第一次启动 Julia Notebook 花了相当长的时间。显示了“Kernel 正在启动,请稍等...”的消息,持续了好几分钟。

如果你运行脚本(使用 Cell | Run All 菜单命令),你的输出应该像下图所示:

显示还会继续列出每个数据集的其他统计信息,如 PetalWidth 等。

请注意 WARNING 消息,提示子库之间存在不兼容问题。即使花了时间安装和更新包,仍然存在未解决的问题。

更有趣的部分是数据点的 plot

我注意到,如果你将鼠标悬停在图形上,会显示网格线和一个滑动条来调整缩放级别(如前面截图右上部分所示)。

所以,就像在 Julia 解释器中运行脚本一样,你会得到输出(带有数字前缀)。Jupyter 已经统计了语句,以便我们可以逐步编号单元格。Jupyter 并没有做任何特殊的处理来打印变量。

我们启动了服务器,创建了一个新的 Notebook,并将其保存为 Julia iris。如果我们在磁盘上打开 IPYNB 文件(使用文本编辑器),可以看到以下内容:

{ 
  "cells": [ 
    ...<similar to previously displayed> 
  ], 
  "metadata": { 
  "kernelspec": { 
   "display_name": "Julia 0.6.1", 
   "language": "julia", 
   "name": "julia-0.6" 
  }, 
  "language_info": { 
   "file_extension": ".jl", 
   "mimetype": "application/julia", 
   "name": "julia", 
   "version": "0.6.1" 
  } 
 }, 
 "nbformat": 4, 
 "nbformat_minor": 1 
} 

这与我们在前几章中看到的其他 Notebook 语言代码略有不同。特别是,metadata 明确指向了要作为 Julia 脚本的脚本单元。

Julia 在 Jupyter 中的限制

我已经在 Jupyter 中编写了 Julia 脚本并访问了不同的 Julia 库,没有遇到任何问题。我没有注意到使用过程中有任何限制或性能下降。我猜测一些非常依赖屏幕的 Julia 功能(例如使用Julia webstack构建网站)可能会受到相同概念冲突的影响。

当我尝试运行 Julia 脚本时,我已经多次看到更新被运行,如下图所示。我不确定为什么他们决定总是更新底层工具,而不是使用当前的工具并让用户决定是否更新库:

我也注意到,一旦打开了 Julia Notebook,即使我已经关闭页面,它仍然会在主页显示“运行中”。我不记得其他脚本语言出现过这种行为。

另一个问题是在我的脚本中尝试使用一个安全的包,例如 plotly。获取凭证的过程似乎很简单,但按照规定的方法将凭证传递给 plotly 在 Windows 下不起作用。我不愿提供在两种环境下都无法工作的示例。

与 Windows 的进一步交互也有限,例如,尝试通过调用标准 C 库访问环境变量,而这些库在 Windows 安装中通常并不存在。

我在使用 Julia 时遇到的另一个问题,无论是在 Jupyter 下运行与否,都存在。当使用一个包时,它会抱怨包中使用的某些特性已经被弃用或改进。作为包的用户,我无法控制这种行为,所以它对我的工作没有帮助。

最后,运行这些脚本需要几分钟时间。所使用的脚本较小,但似乎 Julia 内核启动的时间比较长。

标准 Julia 功能

类似于其他语言中使用的函数,Julia 可以通过使用describe函数对数据执行大部分基本统计,如以下示例脚本所示:

using RDatasets 
describe(dataset("datasets", "iris"))

这个脚本访问了iris数据集,并显示了数据集的汇总统计信息。

如果我们构建一个笔记本,展示如何在iris数据集(在前面的示例中已加载)上使用describe,我们将得到如下显示:

你可以看到数据集中每个变量所生成的标准统计信息。我觉得很有趣的是,数据集中NA值的计数和百分比也被提供了。我发现通常需要在其他语言中进行双重检查,以排除这些数据。这个快速的内置提醒很有用。

警告消息是在抱怨其中一个日期时间库的兼容性问题,尽管它在此笔记本中未被使用。

Jupyter 中的 Julia 可视化

Julia 中最流行的可视化工具是Gadfly包。我们可以通过使用add函数来添加Gadfly包(如本章开头所述):

Pkg.add("Gadfly") 

从那时起,我们可以通过使用以下代码在任何脚本中引用Gadfly包:

using Gadfly 

Julia Gadfly 散点图

我们可以使用plot()函数并采用标准默认设置(没有类型参数)来生成散点图。例如,使用以下简单脚本:

using Gadfly 
srand(111) 
plot(x=rand(7), y=rand(7)) 

在所有使用随机结果的示例中,我们使用了 srand()函数。srand()函数设置随机数种子值,使得本章中的所有结果都可以重现。

我们生成了一个干净整洁的散点图,如下图所示:

我注意到,如果你点击图形右上方出现的?符号,点击图形时,会显示一个消息框,允许对图形进行更精细的控制,如下所示:

  • 在图像上进行平移(特别是当它超出窗口时)。

  • 放大,缩小

  • 重置:

Julia Gadfly 直方图

我们还可以生成其他图表类型,例如,使用以下脚本生成直方图

using Gadfly 
srand(111) 
plot(x=randn(113), Geom.histogram(bincount=10)) 

这个脚本生成了113个随机数,并生成了这些结果的直方图

我们将看到如下截图:

Julia Winston 绘图

Julia 中另一个图形包是 Winston。它具有类似于 Gadfly 的绘图功能(我认为 Gadfly 更为更新)。我们可以使用以下脚本生成一个随机数的类似图形:

using Winston 
# fix the random seed so we have reproducible results 
srand(111) 
# generate a plot 
pl = plot(cumsum(rand(100) .- 0.5), "g", cumsum(rand(100) .- 0.5), "b") 
# display the plot 
display(pl) 

请注意,你需要特别显示该图形。Winston 包假设你希望将图形存储为文件,因此 plot 函数生成一个对象来处理该图形。

将其移动到 Notebook 中,我们得到以下截图:

Julia Vega 绘图

另一个流行的图形包是 VegaVega 的主要特点是能够使用语言原语(如 JSON)来描述图形。Vega 能够生成大多数标准图形。以下是使用 Vega 绘制饼图的示例脚本:

Pkg.add("Vega") 
using Vega 
stock = ["chairs", "tables", "desks", "rugs", "lamps"]; 
quantity = [15, 10, 10, 5, 20]; 
piechart(x = stock, y = quantity) 

在 Jupyter 中生成的输出可能如下所示:

请注意 INFO: Precompiling module Vega. 包。即使该包已经在安装或更新过程中加载,它仍然需要在首次使用时调整库。

在 Jupyter 中生成的图形如下所示:

Vega 在结果显示中为你提供将图形保存为 PNG 的选项。我认为这是一个有用的功能,允许你将生成的图形嵌入到另一个文档中:

Julia PyPlot 绘图

另一个可用的绘图包是 PyPlotPyPlot 是 Python 的标准可视化库之一,并可以直接从 Julia 中访问。我们可以使用以下小脚本来生成一个有趣的可视化:

#Pkg.add("PyPlot") 
using PyPlot 
precipitation = [0,0,0,0,0,0,0,0,0,0,0.12,0.01,0,0,0,0.37,0,0,0,0,0.01,0,0,0,0.01,0.01,0,0.17,0.01,0.11,0.31] 
date = collect(1:31) 
fig = figure(1, figsize=(4, 4)) 
plot(date, precipitation, ".") 
title("Boston Precipitation") 
xlabel("May 2013") 
ylabel("Precipitation") 

在 Jupyter 中生成的输出可能如下所示:

再次,我们看到 Julia 在执行 Notebook 之前更新了一个包,直到最终我们得到了 Precipitation 图形:

有趣的是,波士顿的降水量非常多样——大部分时间没有降水,然后有几天会有大暴雨。

提醒一下:Jupyter 会尝试将大部分输出放入一个小的滚动窗口中。只需点击显示的左侧即可展开滚动面板的全部内容。

Julia 并行处理

Julia 的一个高级内置功能是能够在脚本中使用并行处理。通常,你可以直接在 Julia 中指定要使用的进程数量。然而,在 Jupyter 中,你需要使用 addprocs() 函数来添加额外的进程供脚本使用,例如,看看这个小脚本:

addprocs(1) 
srand(111) 
r = remotecall(rand, 2, 3, 4) 
s = @spawnat 2 1 .+ fetch(r) 
fetch(s) 

它调用了rand,即随机数生成器,执行该代码时,第二个参数被传递给函数调用(进程2),然后将剩余的参数传递给rand函数(使得rand生成一个 3×4 的随机数矩阵)。spawnat是一个宏,用于评估之前提到的进程。接着,fetch用于访问已生成进程的结果。

我们可以在 Jupyter 中查看示例结果,如下截图所示:

所以,这并不是一种剧烈的生成进程类型的计算,但你可以轻松想象在 Jupyter 中可以实现更复杂的进程。

Julia 控制流

Julia 具有完整的控制流。举个例子,我们可以编写一个小函数larger,用于确定两个数字中的较大者:

function larger(x, y)  
    if (x>y)  
        return x 
    end 
    return y 
end 
println(larger(7,8)) 

有几个特点需要注意:

  • if语句的end语句

  • end,作为函数的结束

  • 函数内部语句的缩进

  • if语句中处理条件为真时的缩进

如果我们在 Jupyter 中运行这个,我们会看到预期的输出,如下截图所示:

Julia 正则表达式

Julia 内建了正则表达式处理功能——就像大多数现代编程语言一样。由于正则表达式是 Julia 字符串的基本功能,所以无需使用声明。

我们可以编写一个小脚本,验证一个字符串是否为有效的电话号码,例如:

ismatch(r"^\([0-9]{3}\)[0-9]{3}-[0-9]{4}$", "(781)244-1212") 
ismatch(r"^\([0-9]{3}\)[0-9]{3}-[0-9]{4}$", "-781-244-1212") 

在 Jupyter 中运行时,我们会看到预期的结果。第一个数字符合格式,而第二个则不符合:

Julia 单元测试

作为一门完整的语言,Julia 具有单元测试功能,确保代码按照预期执行。单元测试通常位于测试文件夹中。

Julia 提供的两个标准单元测试函数是FactCheckBase.Test。它们执行相同的操作,但对失败的测试反应不同。FactCheck会生成一条错误信息,但在失败时不会停止处理。如果你提供了错误处理器,错误处理器将接管测试。

Base.Test会抛出异常,并在第一次测试失败时停止处理。从这个角度来看,它可能更适合作为一种运行时测试,而非单元测试,你可以用它来确保参数在合理范围内,或者避免在出现问题之前停止处理。

这两个包是内置在标准的 Julia 发行版中的。

举个例子,我们可以创建一个单元测试的 Notebook,进行相同的测试,并查看不同的错误响应(意味着测试失败)。

对于FactCheck,我们将使用以下脚本:

using FactCheck 
f(x) = x³ 
facts("cubes") do 
    @fact f(2) --> 8 
    @fact f(2) --> 7 
End 

我们正在使用FactCheck包。我们正在测试的简单函数是对一个数字进行立方运算,但它可以是任何函数。我们将测试封装在facts() do...End块中。每个测试都在该块内运行,该块与任何其他块分开——以便将我们的单元测试分组——并且每个测试以@fact为前缀。此外,注意我们正在测试函数结果后跟-->是否是正确的右侧参数。

当我们在 Jupyter 中运行时,我们会看到预期的结果,如下图所示:

您可以看到失败的测试、它为何失败、它出错的行数等信息,还可以查看执行的facts块的总结,即通过的测试数量(Verified)和失败的测试数量(Failed)。请注意,脚本继续运行到下一行。

对于Base.Test,我们有一个类似的脚本:

using Base.Test f(x) = x³ @test f(2) == 8 @test f(2) == 7 

我们正在使用Base.Test包。我们使用的函数定义是再次进行立方运算。然后,每个测试都是单独进行的——不是作为test块的一部分——并且以@test为前缀。在 Jupyter 中运行此脚本时,我们会看到与以下截图中显示的类似结果:

显示了失败的测试信息。然而,在这种情况下,脚本在这一点停止执行。因此,我只会将其作为运行时检查来验证输入格式。

总结

在这一章中,我们增加了在 Jupyter Notebook 中使用 Julia 脚本的能力。我们添加了一个不包含在标准 Julia 安装中的 Julia 库。我们展示了 Julia 的基本功能,并概述了在 Jupyter 中使用 Julia 时遇到的一些限制。我们使用了可用的图形包(包括GadflyWinstonVegaPyPlot)来显示图形。最后,我们看到了并行处理的应用、一个小的控制流示例,以及如何将单元测试添加到 Julia 脚本中。

在下一章中,我们将学习如何在 Jupyter Notebook 中使用 JavaScript。

第五章:Jupyter Java 编程

Java 是一种高级编程语言,最初由 Sun Microsystems 开发,目前归 Oracle 所有。Java 是一种跨平台的编译语言,可以在多种平台上执行。Java 之所以能够跨平台,是因为它生成p-code,该代码由特定版本的 Java 解释执行,这个版本就是Java 虚拟机JVM)。

Java 通过Java 运行时可执行文件JRE)进行分发,适用于只需要执行已编写程序的用户。否则,对于开发 Java 应用程序的用户,则需要Java 开发工具包JDK)。

在本章中,我们将讨论以下主题:

  • 将 Java 内核添加到 Jupyter

  • Java Hello World Jupyter Notebook

  • Jupyter 中的基础 Java

主要的警告是,这在 Windows 环境中不起作用。Java Notebook 在 Windows 上无法启动。

将 Java 内核添加到您的安装中

在本节中,我们将向您的安装中添加 Java 内核。无论您是在 Windows 环境还是 macOS 环境中安装,步骤都非常相似。

Java 内核 IJava 由 Spence Park 开发并维护,地址为github.com/SpencerPark/IJava。使用 Java 内核有一些要求,接下来将介绍。

安装 Java 9 或更高版本

您可以通过在命令行提示符中使用以下命令来检查已安装的 Java 版本:

java --version 

我们需要 9 或更高版本。

此外,安装的版本必须是 JDK。JRE 是不够的。您可以在www.oracle.com/technetwork/java下载最新的 Java 版本。在编写本书时,版本 10 已广泛发布,因此我安装了该版本,如下截图所示:

需要一个 Jupyter 环境

这可能听起来有些重复,但它的广泛性使得 IJava 可以在所有 Jupyter 环境中运行,包括JupyterLabnteract,具体取决于您的需求。

配置 IJava

安装完 Java 后,您需要配置 IJava。

从 GitHub 下载 IJava 项目

我们可以使用以下命令从 GitHub 下载IJava扩展:

> git clone https://github.com/SpencerPark/IJava.git --depth 1

git clone命令将项目文件下载到您所在的IJava目录(在我的情况下,这是我的默认用户目录):

> cd IJava/

这个命令只是将目录切换到已下载的IJava目录。

构建和安装内核

以下是特定操作系统的命令:

  • *nix: chmod u+x gradlew && ./gradlew installKernel

  • Windows: gradlew installKernel

gradlew是 Windows 版本的 Gradle,它是一个流行的脚本系统。Gradle 擅长安装软件。gradlew是在您之前运行的git clone命令的一部分安装的:

如从安装输出中可以看出,IJava 确实需要安装 Java 9。但 Java 9 已不再是 Oracle 支持的版本。我们需要配置工具以使用 Java 10。就我而言,我之前已安装了 Java,并将环境变量 JAVA_HOME 设置为较旧的版本。将环境变量更改为指向 Java 10 安装的路径后,问题解决:

现在,当我们查看内核列表时,我们可以看到可以通过以下命令使用 Java:

>jupyter kernelspec list 

执行上述命令后,我们将看到以下结果:

可用选项

与其他 Java 安装一样,我们可以根据需要设置一些特定于 Java 的环境变量:

设置 默认值 描述
IJAVA_VM_OPTS "" 一个以空格分隔的命令行选项列表,这些选项会在运行代码时传递给 java 命令。例如,我们可以使用 -Xmx128m 来设置堆大小限制,或者使用 -ea 来启用 assert 语句。
IJAVA_COMPILER_OPTS "" 一个以空格分隔的命令行选项列表,这些选项会在编译项目时传递给 javac 命令。例如,-parameters 用于启用保留参数名称以便反射使用。
IJAVA_TIMEOUT 1 一个以毫秒为单位的持续时间,指定长时间运行的代码的超时时间。如果小于零,则禁用超时。
IJAVA_CLASSPATH "" -,一个以文件路径分隔符分隔的 classpath 条目列表,这些条目应该对用户代码可用。
IJAVA_STARTUP_SCRIPTS_PATH "" 一个以文件路径分隔符分隔的 .jshell 脚本文件路径列表,这些脚本将在启动时运行。包括 ijava-jshell-init.jshellijava-magics-init.jshell
IJAVA_STARTUP_SCRIPT "" 在内核启动时执行的 Java 代码块。这可能是类似 import my.utils; 的代码,用于设置一些默认的导入,或者是 void sleep(long time) { try {Thread.sleep(time)} catch (InterruptedException e) {}} 来声明一个默认的 utility 方法,以便在笔记本中使用。

从前面的描述中可以看出,这些都不是运行一个有效的 Java 应用程序所必需的。它们通常用于处理特殊情况。

Jupyter Java 控制台

您可以在 console 模式下运行 Jupyter,这意味着可以直接输入命令行,而不是在浏览器中的新笔记本里输入。命令如下:

jupyter console --kernel=java 

这意味着您可以在控制台窗口中使用 Java 内核启动 Jupyter。我们将看到一个类似下面的窗口,在其中可以输入一些 Java 代码:

命令行界面屏幕上的奇怪界面行反应得像是笔记本的一部分:

String hello = "Hello, Dan" 
hello 

但这不是正常的 Java。行末没有分号。对于单行 Java 语句,分号是可选的。

此外,单行的hello只是对hello变量的引用。我不确定是什么原因导致它在输出中回显。

我们可以将这个代码片段提取到一个 Java 笔记本中,得到类似的结果:

Jupyter Java 输出

Java 实现能够区分stdoutstderr,可以通过以下小代码片段看到这一点:

System.out.println("stdout");
System.err.println("stderr");

在笔记本中运行时,stderr输出会被标记为红色:

Java Optional

许多程序员都被NullPointerException咬过。尽管在 Java 中比 C 或 C++ 更少出现,但它仍然可能发生。现在,Java 引入了Optional字段的概念。一个Optional字段可能有值,也可能没有值。你可以测试是否有值存在,而不是尴尬的null测试。

我们可以通过以下代码片段运行 Optional 的几个方面:

import java.util.Optional; 
public class MyOptional { 
    public static void main() { 
        MyOptional program = new MyOptional(); 
        Integer value1 = null; 
        Integer value2 = 123; 

        //.ofNullable allows null 
        Optional<Integer> a = Optional.ofNullable(value1); 

        //.of does not allow null 
        Optional<Integer> b = Optional.of(value2); 
        System.out.println(program.sum(a,b)); 
    } 

    public Integer sum(Optional<Integer> first, Optional<Integer>
      second) { 
        System.out.println("First parameter present " +
          first.isPresent()); 
        System.out.println("Second parameter present " + 
          second.isPresent()); 
        Integer value1 = first.orElse(1); 
        Integer value2 = second.orElse(1); 
        return value1 + value2; 
    } 
} 
new MyOptional().main(); 

我们有一个标准的类前言,用来根据需要import库。在这个例子中,我们只使用Optional包。

我们将创建一个包含静态main函数的 Java 类。

我们之前定义的main()函数也是非标准的 Java。其签名应该是public static void main(String[] args)

首先,main函数创建了一个类的实例(因为我们稍后会引用类的其他部分)。

然后,我们创建了两个变量,其中一个是邪恶的null值。

Optional有两个方法,做的是相同的事情,但行为不同:

  • ofNullable: 接受一个参数,该参数可以为null,并创建一个Optional字段

  • of: 接受一个参数,该参数不能为null,并创建一个Optional字段

现在我们有两个Optional字段,将它们传递给sum()函数。

sum函数对每个Optional字段使用orElse()函数,期望其中一个或两个为null,并在这些情况下提供安全的处理。

然后,这就是一个简单的数学问题:

正如你在前面的输出中看到的,第一参数是null,但由于orElse函数,函数继续处理结果。

Java 编译器错误

与任何常规 Java 程序一样,你的代码中可能会有编译时错误。Jupyter Java 提供了类似的反馈,行号与笔记本中的行相对应。

例如,当我第一次输入一个示例代码片段时,我们稍后会看到这个例子,出现了一些错误:

第一个错误是尝试从static方法调用sort函数。第二个错误是尝试使用错误的函数名称。这两个错误是开发 Java 应用程序时常见的编译错误类型。

Java Lambda 表达式

Lambda 提供了一种清晰简洁的方式来使用表达式表示一个方法接口。Lambda 通常是单独开发的。Lambda 的形式可以非常接近早期的 Java 实现,也可以完全不同,正如以下示例所示。我们使用越来越简洁的语法来开发 Lambda:

这三个 Lambda 表达式执行相同的步骤(如前面的输出所示)。然而,实现方式逐渐变得更加不符合传统 Java 风格。

Java 集合

Java 集合在最近的几个版本中经历了重大重构。现在,你可以使用 Lambda 函数来描述你的比较点。如果该对象具有内置的compareTo函数(所有标准 Java 对象都有),那么就完成了。

在这个例子中,我们构建了一个包含字符串(国家名称)的列表,并将该列表传递给Collections.sort例程。sort例程变得非常简单,只是调用 Java 中String类型的内置compareTo函数:

当我们运行时,可以看到结果按排序顺序输出。

很可能有一种方法可以在不修改传入数组的情况下做到这一点。

Java 流

Java 流(streams)是 Java 8 中的一个重大改进。现在,Java 能够以函数式的方式处理信息流。在这个例子中,我们将通过几个小示例来展示该特性的强大功能。

我们使用的代码片段如下:

public class MyStreams { 

    public static void main(String[] args) { 
          List<Integer> numbers = new ArrayList<Integer>(); 
          numbers.add(3); 
          numbers.add(-1); 
          numbers.add(3); 
          numbers.add(17); 
          numbers.add(7); 

          System.out.println("Numbers greater than 2"); 
          numbers.stream() 
                .filter(number -> number > 2) 
                .forEach(number -> System.out.println(number)); 

          System.out.println("number size = " +
            numbers.stream().count()); 

          Integer big = numbers.stream().max((n1,n2) -> 
            Integer.compare(n1, n2)).get(); 
          System.out.println("biggest number = " + big); 

          Integer small = numbers.stream().min((n1,n2) -> 
            Integer.compare(n1, n2)).get(); 
          System.out.println("smallest number = " + small); 

          System.out.println("Sorted"); 
          numbers.stream() 
                .sorted((n1,n2) -> Integer.compare(n1, n2)) 
                .forEach(number -> System.out.println(number)); 

          Integer total = numbers.stream() 
                      .collect(Collectors.summingInt(i -> i)) 
                      .intValue(); 
          System.out.println("Total " + total); 

          String summary = numbers.stream() 
                .collect(Collectors.summarizingInt(i -> i)) 
                .toString(); 
          System.out.println("Summary " + summary); 

          System.out.println("Squares"); 
          numbers.stream() 
                .map(i -> i * i) 
                .forEach(i -> System.out.println(i)); 

          System.out.println("Growth"); 
          numbers.stream() 
                .flatMap(i -> build(i)) 
                .sorted() 
                .forEach(i -> System.out.println(i)); 

          System.out.println("Distinct growth"); 
          numbers.stream() 
                .flatMap(i -> build(i)) 
                .sorted() 
                .distinct() 
                .forEach(i -> System.out.println(i)); 
    } 

    static Stream<Integer> build(Integer i) { 
          List<Integer> t = new ArrayList<Integer>(); 
          t.add(i); 
          t.add(i*i); 
          t.add(i*i*i); 
          return t.stream(); 
    } 
} 

该代码使用一个包含数字的集合(stream)进行多个流操作。流内置了许多更多的函数:

  1. 首先,我们使用filter挑选出感兴趣的元素。

  2. 我们使用count来找出流中有多少个元素。

  3. 我们使用一个 Lambda 函数来查找流中的最小元素。

  4. 接下来,我们使用另一个 Lambda 表达式对流元素进行排序。

  5. 然后,我们使用collect并使用summingInt来将所有元素相加。

  6. 该流的汇总已生成——这是流的内置功能。

  7. 最后,我们使用mapflatMap对流中的元素进行投影(增长)。

编码和输出如下所示(我添加了水平线来分隔输出,使其更易于阅读):

我已截断显示,剩下的与预期一致。

类似地,在以下输出中,我并没有显示所有的输出:

Java 汇总统计

Java 可以为集合生成汇总统计数据。我们可以获取Iris数据集并将其放入集合中,然后直接生成统计数据。

我已经将文件从archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data复制过来,以便使处理过程更为顺畅。

我们读取Iris数据,然后调用集合生成汇总。

这个例子的代码如下:

import java.io.IOException; 
import java.nio.file.FileSystems; 
import java.nio.file.Files; 
import java.nio.file.Path; 
import java.text.DateFormat; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Map; 
import java.util.Optional; 
import java.util.regex.Pattern; 
import java.util.stream.Collectors; 
import java.util.stream.Stream; 

public class Iris { 

    public Iris(Double sepalLength, Double sepalWidth, Double 
     petalLength, Double petalWidth, String irisClass) { 
        this.sepalLength = sepalLength; 
        this.sepalWidth = sepalWidth; 
        this.petalLength = petalLength; 
        this.petalWidth = petalWidth; 
        this.irisClass = irisClass; 
    } 

    private Double sepalLength; 
    private Double sepalWidth; 
    private Double petalLength; 
    private Double petalWidth; 
    private String irisClass; 

    public Double getSepalLength() { 
        return this.sepalLength; 
    } 

    //other getters and setters TBD 
} 

public class JavaIris { 

    public void test() { 

        //file originally at http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data 
        Path path = FileSystems 
            .getDefault() 
            .getPath("/Users/ToomeyD/iris.csv"); 
        List<Iris> irises = load(path); 

        //produce summary statistics for sepal length values 
        String sepalLengthSummary = irises.stream() 
            .collect(Collectors.summarizingDouble(Iris::getSepalLength)) 
            .toString(); 
        System.out.println("\nSepal Length Summary " + sepalLengthSummary); 
    } 

    public List<Iris> load(Path path) { 
        List<Iris> irises = new ArrayList<Iris>(); 

        try (Stream<String> stream = Files.lines(path)) { 
            stream.forEach((line) -> { 
                System.out.println(line); 

                //put each field into array 
                List<String> fields = new ArrayList<String>(); 
                Pattern.compile(",") 
                    .splitAsStream(line) 
                    .forEach((field) -> fields.add(field)); 

                //build up the iris values 
                Double sepalLength = new Double(fields.get(0)); 
                Double sepalWidth = new Double(fields.get(1)); 
                Double petalLength = new Double(fields.get(2)); 
                Double petalWidth = new Double(fields.get(3)); 
                String irisClass = fields.get(4); 
                Iris iris = new Iris(sepalLength, sepalWidth,
                 petalLength, petalWidth, irisClass); 

                //add to array 
                irises.add(iris); 
            }); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 

        return irises; 
    } 
} 

new JavaIris().test(); 

该代码正在将iris数据的每一行解析为一个Iris对象,并将该Iris对象添加到一个数组中。

然后,主程序调用集合生成摘要。

代码看起来如下,其中我们将Iris作为一个单独的对象:

接下来,主程序的编码读取花卉信息并生成统计数据如下:

输出的尾部看起来如下:

这种处理在 Jupyter 中其他可用的引擎中要容易得多。

摘要

在本章中,我们看到了将 Java 引擎安装到 Jupyter 中的步骤。我们看到了 Java 在 Jupyter 中提供的不同输出展示的示例。然后,我们调查了使用Optional字段。我们看到了 Java 在 Jupyter 中的编译错误是什么样子。接下来,我们看到了几个 lambda 表达式的示例。我们将集合用于几个目的。最后,我们为Iris数据集中的一个数据点生成了汇总统计。

在下一章中,我们将看看如何创建可用于笔记本中的交互式小部件。

第六章:Jupyter JavaScript 编程

JavaScript 是一种高级、动态、无类型并且解释型的编程语言。许多基于 JavaScript 的衍生语言应运而生。对于 Jupyter 来说,底层的 JavaScript 实际上是 Node.js。Node.js 是一个基于事件的框架,使用 JavaScript,可以用于开发大型可扩展的应用程序。需要注意的是,这与本书前面介绍的主要用于数据分析的语言不同(虽然 Python 也是一种通用语言,但它有着明确的数据分析能力)。

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

  • 向 Jupyter 添加 JavaScript 包

  • JavaScript Jupyter Notebook

  • Jupyter 中的基本 JavaScript

  • Node.js d3

  • Node.js stats-analysis

  • Node.js JSON 处理

  • Node.js canvas

  • Node.js plotly

  • Node.js 异步线程

  • Node.js decision-tree

向你的安装中添加 JavaScript 脚本

在本节中,我们将安装 macOS 和 Windows 上的 JavaScript 脚本。对于每个环境,Jupyter 安装中启用 JavaScript 脚本的步骤是不同的。macOS 的安装非常简洁,而 Windows 的安装仍然在变化中,我预计以下说明将随着时间变化。

在 macOS 或 Windows 上向 Jupyter 添加 JavaScript 脚本

我按照 github.com/n-riesco/iJavaScript 中的说明加载了 Anaconda 的 JavaScript 引擎。步骤如下:

conda install nodejs 
npm install -g iJavaScript 
ijsinstall 

此时,启动 Jupyter 时会提供 JavaScript(Node.js)引擎作为选择,如下截图所示:

JavaScript Hello World Jupyter Notebook

安装完成后,我们可以通过点击“New”菜单并选择 JavaScript 来尝试第一个 JavaScript Notebook。我们将把 Notebook 命名为 Hello, World! 并在脚本中输入以下行:

var msg = "Hello, World!" 
console.log(msg) 

这个脚本设置了一个变量并显示该变量的内容。输入脚本并运行(单击“Cell | Run All”),我们将看到如下截图所示的 Notebook 界面:

我们应该指出这一页的一些亮点:

  • 我们在右上角看到熟悉的语言标识,表示正在使用的脚本类型

  • Notebook 中的每一行都有输出

  • 更重要的是,我们可以看到 Notebook 的真实输出(如第一个行所示),其中字符串被回显

  • 否则,Notebook 看起来和我们见过的其他类型一样熟悉

如果查看磁盘上的 Notebook 内容,我们也能看到类似的结果:

{ 
  "cells": [ 
    <<same format as seen earlier for the cells>> 
  ], 
  "metadata": { 
    "kernelspec": { 
      "display_name": "JavaScript (Node.js)", 
      "language": "JavaScript", 
      "name": "JavaScript" 
    }, 
    "language_info": { 
      "file_extension": ".js", 
      "mimetype": "application/JavaScript", 
      "name": "JavaScript", 
      "version": "8.9.3" 
    } 
  }, 
  "nbformat": 4, 
  "nbformat_minor": 2 
} 

因此,通过使用相同的 Notebook 和 JSON 文件格式,Jupyter 通过适当修改 metadatalanguage_info 值,提供了不同的语言供 Notebook 使用。

向 Jupyter 添加 JavaScript 包

JavaScript 语言通常不会安装额外的包,它通过程序中的运行时包含指令来引用其他包。其他包可以通过网络引用,也可以本地复制到你的环境中。假设通过 CDN 访问库是一种更高效、更快速的机制。

然而,Node.js 为 JavaScript 语法添加了所需的动词。在这个例子中,你的代码需要加载另一个模块,假设它已经安装在你当前的环境中。要安装另一个模块,可以使用 npm,例如,在以下命令中:

npm install name-of-module 

这将安装被引用的模块(包括所需的嵌入式包)到你的计算机上,以便 require 语句可以正常工作。

Jupyter 中的基础 JavaScript

JavaScript,甚至 Node.js,通常并不以数据处理著称,而是以应用(网站)开发为主。这一点与我们之前讨论的语言有所不同。然而,本章的例子将重点介绍如何使用 JavaScript 进行应用开发,并结合数据访问和分析功能。

Jupyter 中的 JavaScript 限制

JavaScript 最初是专门为了在 HTML 页面中进行脚本编写而设计的,通常是在客户端(浏览器中)。因此,它被构建为能够操作页面上的 HTML 元素。为进一步扩展这一功能,尤其是使用 Node.js 等扩展,已经开发了多个包,甚至可以用来创建 Web 服务器。

在 Jupyter 中使用任何 HTML 操作和生成特性时都会遇到障碍,因为 Jupyter 期望控制用户的呈现方式。

Node.js d3 包

d3 包具有数据访问功能。在这个例子中,我们将从一个制表符分隔的文件中读取数据并计算平均值。请注意使用了变量名以 _ 开头来表示 lodash。以下划线开头的变量名通常被认为是私有的。然而,在这个例子中,它仅仅是我们使用的包名的变体,即 lodash,或者叫做 underscore。lodash 也是一个广泛使用的 utility 包。

为了执行这个脚本,我做了如下操作:

  • 安装 d3

  • 安装 lodash

  • 安装 isomorphic-fetchnpm install --save isomorphic-fetch es6-promise

  • 导入 isomorphic-fetch

我们将使用的脚本如下:

var fs = require("fs");
var d3 = require("d3");
var _ = require("lodash");
var _ = require("isomorphic-fetch");

//read and parse the animals file
console.log("Animal\tWeight");
d3.csv("http://www.dantoomeysoftware.com/data/animals.csv", function(data) {
    console.log(data.name + '\t' + data.avg_weight);
});

这假设我们已经使用 npm 加载了 fsd3 包,如前面的脚本所述。

对于这个例子,我在我的网站上创建了一个 data 目录,因为我们输入的 URL 被期望是一个绝对 URL,并在该目录下创建了一个 CSV 文件(animal.csv):

Name,avg_weight 
Lion,400 
Tiger,400 
Human,150 
Elephant,2000 

如果我们将这个脚本加载到一个 Notebook 中并运行,它将输出以下内容,正如预期:

需要注意的是,d3 函数(实际上有很多)是异步操作的。在我们的例子中,我们只是打印文件的每一行。你可以想象更复杂的功能。

Node.js 数据分析包

stats-analysis 包包含了你可能需要对数据执行的许多常见统计操作。你需要使用 npm 安装这个包,正如之前所解释的那样。

如果我们有一小组人的体温数据可以使用,我们可以通过使用这个脚本快速获得数据的一些统计信息:

const stats = require("stats-analysis"); 

var arr = [98, 98.6, 98.4, 98.8, 200, 120, 98.5]; 

//standard deviation 
var my_stddev = stats.stdev(arr).toFixed(2); 

//mean 
var my_mean = stats.mean(arr).toFixed(2); 

//median 
var my_median = stats.median(arr); 

//median absolute deviation 
var my_mad = stats.MAD(arr); 

// Get the index locations of the outliers in the data set 
var my_outliers = stats.indexOfOutliers(arr); 

// Remove the outliers 
var my_without_outliers = stats.filterOutliers(arr); 

//display our stats 
console.log("Raw data is ", arr); 
console.log("Standard Deviation is ", my_stddev); 
console.log("Mean is ", my_mean); 
console.log("Median is ", my_median); 
console.log("Median Abs Deviation is " + my_mad); 
console.log("The outliers of the data set are ", my_outliers); 
console.log("The data set without outliers is ", my_without_outliers); 

当这个脚本输入到 Notebook 中时,我们会得到类似于下图所示的内容:

执行时,我们得到如下截图所示的结果:

有趣的是,98.5 被认为是一个异常值。我猜测命令中有一个可选参数可以改变使用的限制。否则,结果与预期一致。

异常值来自于将原始数据当作纯数学项处理。所以,技术上来说,从提供的数据中,我们已经识别出异常值。然而,我们可能会使用不同的方法来确定异常值,了解人类体温的领域平均值。

Node.js JSON 处理

在这个示例中,我们将加载一个 JSON 数据集,并对数据进行一些标准的操作。我引用了来自 www.carqueryapi.com/api/0.3/?callback=?&cmd=getModels&make=ford 的 FORD 模型列表。我不能直接引用这个,因为它不是一个扁平化文件,而是一个 API 调用。因此,我将数据下载到一个名为 fords.json 的本地文件中。此外,API 调用的输出会将 JSON 包裹成这样:?(json);。在解析之前,这部分需要被移除。

我们将使用的脚本如下。在脚本中,JSON 是 Node.js 的内建包,因此我们可以直接引用这个包。JSON 包提供了处理 JSON 文件和对象所需的许多标准工具。

这里值得关注的是 JSON 文件读取器,它构建了一个标准的 JavaScript 对象数组。每个对象的属性可以通过 name 进行引用,例如 model.model_name

//load the JSON dataset 
//http://www.carqueryapi.com/api/0.3/?callback=?&cmd=getModels&make=ford 
var fords = require('/Users/dtoomey/fords.json'); 

//display how many Ford models are in our data set 
console.log("There are " + fords.Models.length + " Ford models in the data set"); 

//loop over the set 
var index = 1 
for(var i=0; i<fords.Models.length; i++) { 

    //get this model 
    var model = fords.Models[i]; 

    //pull it's name 
    var name = model.model_name; 

    //if the model name does not have numerics in it 
    if(! name.match(/[0-9]/i)) { 
        //display the model name 
        console.log("Model " + index + " is a " + name); 
        index++; 
    } 

    //only display the first 5 
    if (index>5) break; 
} 

如果我们将这个脚本引入一个新的 Notebook 条目中,我们会得到以下截图:

当脚本执行时,我们会得到预期的结果,如下所示:

Node.js canvas 包

canvas 包用于在 Node.js 中生成图形。我们可以使用来自 canvas 包主页的示例 (www.npmjs.com/package/canvas)。

首先,我们需要安装 canvas 及其依赖项。不同操作系统的安装指南可以在主页上找到,但它与我们之前见过的工具非常相似(我们已经见过 macOS 的版本):

npm install canvas 
brew install pkg-config cairo libpng jpeg giflib 

这个示例在 Windows 中无法运行。Windows 安装要求安装 Microsoft Visual C++。我尝试了多个版本,但未能成功。

在机器上安装了 canvas 包后,我们可以使用一个小的 Node.js 脚本来创建图形:

// create a canvas 200 by 200 pixels 
var Canvas = require('canvas') 
  , Image = Canvas.Image 
  , canvas = new Canvas(200, 200) 
  , ctx = canvas.getContext('2d') 
  , string = "Jupyter!"; 

// place our string on the canvas 
ctx.font = '30px Impact'; 
ctx.rotate(.1); 
ctx.fillText(string, 50, 100); 

var te = ctx.measureText(string); 
ctx.strokeStyle = 'rgba(0,0,0,0.5)'; 
ctx.beginPath(); 
ctx.lineTo(50, 102); 
ctx.lineTo(50 + te.width, 102); 
ctx.stroke(); 

//create an html img tag, with embedded graphics 
console.log('<img src="img/' + canvas.toDataURL() + '" />'); 

这个脚本在 canvas 上写入字符串 Jupyter!,然后生成带有图形的 HTML img 标签。

在 Notebook 中运行脚本后,我们会得到 img 标签作为输出:

我们可以将 img 标签保存为 HTML 页面,效果如下:

<html>
 <body>
 <img src="img/png;base64,iVBORw0KGgo<the rest of the tag>CC" />
 </body>
 </head> 

然后,我们可以用浏览器打开 HTML 文件来显示我们的图形:

Node.js plotly 包

plotly 是一个与大多数包不同的工具。使用此软件时,您必须注册一个 username 账户,这样您就能获得一个 api_key(在 plot.ly/)。然后将 usernameapi_key 放入您的脚本中。此时,您可以使用 plotly 包的所有功能。

首先,像其他包一样,我们需要先安装它:

npm install plotly 

安装完成后,我们可以根据需要引用 plotly 包。使用一个简单的脚本,我们可以使用 plotly 生成一个 histogram(直方图):

//set random seed 
var seedrandom = require('seedrandom'); 
var rng = seedrandom('Jupyter'); 
//setup plotly 
var plotly = require('plotly')(username="<username>", api_key="<key>") 
var x = []; 
for (var i = 0; i < 500; i ++) { 
    x[i] = Math.random(); 
} 
require('plotly')(username, api_key); 
var data = [ 
  { 
    x: x, 
    type: "histogram" 
  } 
]; 
var graphOptions = {filename: "basic-histogram", fileopt: "overwrite"}; 
plotly.plot(data, graphOptions, function (err, msg) { 
    console.log(msg); 
}); 

一旦在 Jupyter 中加载并运行为 Notebook,我们将看到以下界面:

与创建本地文件或仅在屏幕上显示图形不同,任何创建的图形都会存储在 Plotly 上。plot 命令的输出是一组返回值,其中最重要的是您可以访问图形的 URL。

理想情况下,我应该能够通过提供的 URL 访问我的图形(直方图),URL 为 plot.ly/~dantoomey/1。插入 ~ 字符后,返回的 URL 按预期工作。然而,当我浏览 Plotly 网站时,我发现我的图形位于与预期略有不同的路径中。所有图形都在我的主页上,在我的情况下是 plot.ly/~dantoomey。现在,我可以访问所有的图形。直方图如下所示:

Node.js 异步线程

Node.js 内置了创建线程并使其异步执行的机制。借用 book.mixu.net/node/ch7.html 中的示例,我们可以得到以下代码:

//thread function - invoked for every number in items array 
function async(arg, callback) { 
  console.log('cube \''+arg+'\', and return 2 seconds later'); 
  setTimeout(function() { callback(arg * 3); }, 2000); 
} 

//function called once - after all threads complete 
function final() { console.log('Done', results); } 

//list of numbers to operate upon 
var items = [ 0, 1, 1, 2, 3, 5, 7, 11 ]; 

//results of each step 
var results = []; 

//loop the drives the whole process 
items.forEach(function(item) { 
  async(item, function(result){ 
    results.push(result); 
    if(results.length == items.length) { 
      final(); 
    } 
  }) 
}); 

该脚本创建了一个对数字进行操作的异步函数。对于每个数字(item),我们调用内联函数,将数字传递给该函数,函数将该数字应用于 results 列表。在这个例子中,它只是将数字乘以三并等待两秒钟。主循环(在脚本的底部)为列表中的每个数字创建一个线程,然后等待它们全部完成后再调用 final() 例程。

笔记本页面如下所示:

当我们运行脚本时,我们会得到类似于以下的输出:

很奇怪的是,看到最后一行输出(来自 final() 例程)显示时有延迟,尽管我们在之前编写 async 函数时明确指定了添加延迟。

此外,当我尝试其他函数时,例如对每个数字进行立方运算,results 列表的顺序发生了很大的变化。我本来没想到这么基础的数学运算会对性能产生影响。

Node.js 的 decision-tree 包

decision-tree 包是一个机器学习包的例子。它可以在 www.npmjs.com/package/decision-tree 获取。该包可以通过以下命令安装:

npm install decision-tree 

我们需要一个数据集来用于训练/开发我们的决策树。我使用了以下网页中的汽车 MPG 数据集:alliance.seas.upenn.edu/~cis520/wiki/index.php?n=Lectures.DecisionTrees。它似乎无法直接获得,所以我将其复制到 Excel 并保存为本地 CSV 文件。

机器学习的逻辑非常相似:

  • 加载我们的数据集

  • 将数据集划分为训练集和测试集

  • 使用训练集来开发我们的模型

  • 在测试集上测试模式

通常,您可能会将三分之二的数据用于训练,三分之一用于测试。

使用 decision-tree 包和 car-mpg 数据集,我们将拥有一个类似于以下的脚本:

//Import the modules 
var DecisionTree = require('decision-tree'); 
var fs = require("fs"); 
var d3 = require("d3"); 
var util = require('util'); 

//read in the car/mpg file 
fs.readFile("/Users/dtoomey/car-mpg.csv", "utf8", function(error, data) { 

    //parse out the csv into a dataset 
    var dataset = d3.tsvParse(data); 

    //display on screen - just for debugging 
    //console.log(JSON.stringify(dataset)); 

    var rows = dataset.length; 
    console.log("rows = " + rows); 
    var training_size = rows * 2 / 3; 
    console.log("training_size = " + training_size); 
    var test_size = rows - training_size; 
    console.log("test_size = " + test_size); 

    //Prepare training dataset 
    var training_data = dataset.slice(1, training_size); 

    //Prepare test dataset 
    var test_data = dataset.slice(training_size, rows); 

    //Setup Target Class used for prediction 
    var class_name = "mpg"; 

    //Setup Features to be used by decision tree 
    var features = ["cylinders","displacement","horsepower", "weight", "acceleration", "modelyear", "maker"]; 

    //Create decision tree and train model 
    var dt = new DecisionTree(training_data, class_name, features); 
    console.log("Decision Tree is " + util.inspect(dt, {showHidden: false, depth: null})); 

    //Predict class label for an instance 
    var predicted_class = dt.predict({ 
        cylinders: 8, 
        displacement: 400, 
        horsepower: 200, 
        weight: 4000, 
        acceleration: 12, 
        modelyear: 70, 
        maker: "US" 
    }); 
    console.log("Predicted Class is " + util.inspect(predicted_class, {showHidden: false, depth: null})); 

    //Evaluate model on a dataset 
    var accuracy = dt.evaluate(test_data); 
    console.log("Accuracy is " + accuracy); 

    //Export underlying model for visualization or inspection 
    var treeModel = dt.toJSON(); 
    console.log("Decision Tree JSON is " + util.inspect(treeModel, {showHidden: false, depth: null})); 
}); 

console.log 被广泛用于显示关于正在进行的处理过程的渐进信息。我进一步使用了 util() 函数,以便显示正在使用的对象成员。

这些包也必须使用 npm 进行安装。

如果我们在笔记本中运行它,我们会得到如下输出顶部显示的结果:

在这里,系统仅记录它在文件中找到的条目,并根据我们分配的不同变量呈现决策点。例如,当 cylinders8displacement400 时,mpgBad,依此类推。

我们得到了一个模型,用于确定车辆的 mpg 是否可接受,这取决于车辆的特征。在这种情况下,正如结果中所示,我们有一个不太好的预测器。

总结

在本章中,我们学习了如何将 JavaScript 添加到我们的 Jupyter Notebook 中。我们了解了在 Jupyter 中使用 JavaScript 的一些限制。我们还看了一些典型的 Node.js 编程包的示例,包括用于图形的d3,用于统计分析的stats-analysis,内置的 JSON 处理,canvas 用于创建图形文件,以及 plotly,它用于通过第三方工具生成图形。我们还看到了如何在 Jupyter 中使用 Node.js 开发多线程应用程序。最后,我们看到了如何进行机器学习以开发决策树。

在下一章中,我们将学习如何创建可以在你的 Notebook 中使用的交互式小部件。

第七章:Jupyter Scala

Scala 已经变得非常流行。它是建立在 Java 之上的(因此具有完全的互操作性,包括在 Scala 代码中嵌入 Java)。然而,它的语法更加简洁直观,改进了 Java 中的一些怪异之处。

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

  • 安装 Scala 用于 Jupyter

  • 使用 Scala 特性

安装 Scala 内核

macOS 的安装步骤如下(摘自 developer.ibm.com/hadoop/2016/05/04/install-jupyter-notebook-spark):

我无法在 Windows 10 机器上使用 Scala 内核的步骤。

  1. 使用以下命令安装 git
yum install git 
  1. scala 包复制到本地:
git clone https://github.com/alexarchambault/jupyter-scala.git 
  1. 通过运行以下命令安装 sbt 构建工具:
sudo yum install sbt 
  1. jupyter-scala 目录移动到 scala 包:
cd jupyter-scala 
  1. 构建包:
sbt cli/packArchive 
  1. 要启动 Scala shell,请使用以下命令:
./jupyter-scala 
  1. 通过运行此命令检查已安装的内核(现在应该可以在列表中看到 Scala):
 jupyter kernelspec list  
  1. 启动 Jupyter Notebook:
jupyter notebook 
  1. 现在您可以选择使用 Scala 2.11 的 shell。

此时,如果启动 Jupyter,您将看到 Scala 被列出:

如果我们创建一个 Scala Notebook,我们最终会看到熟悉的布局,图标显示我们正在运行 Scala,并且引擎类型字符串标识为 Scala。内核名称也在 Jupyter 的 URL 中指定:

所以,在将我们的 Notebook 命名为 Scala Notebook 并保存后,我们将在主页上看到熟悉的 Notebook 显示,其中新 Notebook 被命名为 Scala Notebook.ipynb

如果我们查看 .ipynb 文件,我们可以看到类似于其他 Notebook 类型的标记,带有 Scala 特有的标记:

{ 
 "cells": [ 
  { 
   "cell_type": "code", 
   "execution_count": null, 
   "metadata": {}, 
   "outputs": [], 
   "source": [] 
  } 
 ], 
 "metadata": { 
  "kernelspec": { 
   "display_name": "Scala", 
   "language": "scala", 
   "name": "scala" 
  }, 
  "language_info": { 
   "codemirror_mode": "text/x-scala", 
   "file_extension": ".scala", 
   "mimetype": "text/x-scala", 
   "name": "scala211", 
   "nbconvert_exporter": "script", 
   "pygments_lexer": "scala", 
   "version": "2.11.11" 
  } 
 }, 
 "nbformat": 4, 
 "nbformat_minor": 2 
} 

现在,我们可以在某些单元格中输入 Scala 代码。根据之前章节中的语言示例,我们可以输入以下内容:

val name = "Dan" 
val age = 37 
show(name + " is " + age) 

Scala 有可变变量(var)和不可变变量(val)。我们不打算更改字段,因此它们是 val。最后一条语句 show 是 Jupyter 为 Scala 提供的扩展,用于显示一个变量。

如果我们在 Jupyter 中运行这个脚本,我们将看到如下内容:

在单元格的输出区域,我们看到预期的 Dan is 37。有趣的是,Scala 还会在脚本的这一点显示每个变量的当前类型和值。

Jupyter 中的 Scala 数据访问

在加利福尼亚大学(Irvine)的网站上有一个 iris 数据集的副本,网址是 archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data。我们将访问此数据并进行几个统计操作:

Scala 代码如下:

import scala.io.Source;
//copied file locally https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
val filename = "iris.data"
//println("SepalLength, SepalWidth, PetalLength, PetalWidth, Class");
val array = scala.collection.mutable.ArrayBuffer.empty[Float]
for (line <- Source.fromFile(filename).getLines) {
    var cols = line.split(",").map(_.trim);
//println(s"${cols(0)}|${cols(1)}|${cols(2)}|${cols(3)} |${cols(4)}");
   val i = cols(0).toFloat
   array += i;
}
val count = array.length;
var min:Double = 9999.0;
var max:Double = 0.0;
var total:Double = 0.0;
for ( x <- array ) {
    if (x < min) { min = x; }
    if (x > max) { max = x; }
    total += x;
}
val mean:Double = total / count;

似乎存在通过互联网访问 CSV 文件的问题。所以,我将文件复制到本地,放入与 Notebook 相同的目录中。

关于这个脚本,有一个值得注意的地方是,我们不必像通常那样将 Scala 代码包裹在一个对象中,因为 Jupyter 提供了wrapper类。

当我们运行脚本时,看到这些结果:

这是iris数据的不同版本,因此,我们看到的统计结果与之前有所不同。

Scala 数组操作

Scala 没有 pandas,但我们可以通过自己的编码来模拟其中的一些逻辑。我们将使用第二章中使用的Titanic数据集,Jupyter Python 脚本,该数据集来自titanic-gettingStarted/fdownload/train.csv,并已下载到我们的本地空间。

我们可以使用类似于第二章中Jupyter Python 脚本使用的代码来处理 pandas:

import scala.io.Source; 

val filename = "train.csv" 
//PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked 
//1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S 

var males = 0 
var females = 0 
var males_survived = 0 
var females_survived = 0 
for (line <- Source.fromFile(filename).getLines) { 
    var cols = line.split(",").map(_.trim); 
    var sex = cols(5); 
    if (sex == "male") {  
        males = males + 1; 
        if (cols(1).toInt == 1) { 
            males_survived = males_survived + 1; 
        } 
    } 
    if (sex == "female") {  
        females = females + 1;  
        if (cols(1).toInt == 1) { 
            females_survived = females_survived + 1; 
        } 
    }     
} 
val mens_survival_rate = males_survived.toFloat/males.toFloat 
val womens_survival_rate = females_survived.toFloat/females.toFloat 

在代码中,我们逐行读取文件,解析出列(它是 CSV 格式),然后根据数据的sex列进行计算。有趣的是,Scala 的数组不是零索引的。

当我们运行这个脚本时,我们看到的结果与之前非常相似:

所以,我们看到女性的生存率显著更高。我认为关于“妇女和儿童优先”的故事是事实。

Scala 随机数在 Jupyter 中的使用

在这个例子中,我们模拟掷骰子并统计每种组合出现的次数。然后我们展示了一个简单的直方图来做说明。

脚本如下:

val r = new scala.util.Random 
r.setSeed(113L) 
val samples = 1000 
var dice = new ArrayInt 
for( i <- 1 to samples){ 
    var total = r.nextInt(6) + r.nextInt(6) 
    dice(total) = dice(total) + 1 
} 
val max = dice.reduceLeft(_ max _) 
for( i <- 0 to 11) { 
    var str = "" 
    for( j <- 1 to dice(i)/3) { 
        str = str + "X" 
    } 
    print(i+1, str, "\n") 
} 

我们首先引入了 Scala 的Random库。我们设置了种子(以便得到可重复的结果)。我们进行1000次掷骰。每次掷骰时,我们都会增加一个计数器,记录骰子一和骰子二上的点数之和出现的次数。然后我们展示结果的简化直方图。

Scala 有许多快捷方法,可以快速扫描列表/集合,如reduceLeft(_ max _)语句所示。我们也可以通过在reduceLeft语句中使用min代替max来找到最小值。

当我们运行脚本时,我们得到这些结果:

我们可以看到粗略的直方图,以及脚本中标量变量的当前值显示。请注意,我将结果除以三,以便让结果能适应一页显示。

Scala 闭包

闭包就是一个函数。这个函数的返回值依赖于函数外部声明的变量值。

我们可以通过以下小脚本进行说明:

var factor = 7
val multiplier = (i:Int) => i * factor
val a = multiplier(11)
val b = multiplier(12)

我们定义了一个名为multiplier的函数。该函数期望一个Int类型的参数。对于每个参数,我们将其与外部factor变量相乘。

我们看到以下结果:

闭包的使用感觉很好,它们做了你所期望的事情,而且非常简单直接。

Scala 高阶函数

高阶函数要么接受其他函数作为参数,要么将一个函数作为结果返回。

我们可以使用以下示例脚本:

def squared(x: Int): Int = x * x
def cubed(x: Int): Int = x * x * x

def process(a: Int, processor: Int => Int): Int = {processor(a) }

val fiveSquared = process(5, squared)
val sevenCubed = process(7, cubed)

我们定义了两个函数:一个对传入的数字进行平方,另一个对传入的数字进行立方。

接下来,我们定义了一个高阶函数,该函数接受一个数字进行处理并应用一个处理器。

最后,我们调用每个函数。例如,我们调用process()并传入5squared函数。process()函数将5传递给squared()函数并返回结果:

我们利用 Scala 的引擎自动打印变量值,以查看预期的结果。

这些函数并没有做太多的事情。当我运行它们时,结果显示花费了几秒钟。我认为在 Scala 中使用高阶函数会导致性能上的大幅下降。

Scala 模式匹配

Scala 具有非常有用的内置模式匹配。模式匹配可以用于测试整个值、对象的部分内容等的精确匹配和/或部分匹配。

我们可以使用这个示例脚本作为参考:

def matchTest(x: Any): Any = x match { 
  case 7 => "seven" 
  case "two" => 2 
  case _ => "something" 
} 
val isItTwo = matchTest("two") 
val isItTest = matchTest("test") 
val isItSeven = matchTest(7) 

我们定义了一个名为matchTest的函数。matchTest函数可以接受任何类型的参数,并返回任何类型的结果。(不确定这是否是实际编程中的情况)

关键字是match。这意味着函数会遍历选择列表,直到找到与传入的x值匹配的项,然后返回。

如你所见,我们有数字和字符串作为输入和输出。

最后的case语句是一个通配符,_catchall,这意味着如果代码执行到这一行,它将匹配任何参数。

我们可以看到以下输出:

Scala case

case类是一种简化类型,可以在不调用new Classname(..)的情况下使用。例如,我们可以有以下脚本,定义一个case类并使用它:

case class Car(brand: String, model: String) 
val buickLeSabre = Car("Buick", "LeSabre") 

所以,我们有一个名为Carcase类。我们创建了这个类的一个实例,称为buickLeSabre

case类最适用于模式匹配,因为我们可以轻松构造复杂对象并检查其内容。例如:

def carType(car: Car) = car match { 
  case Car("Honda", "Accord") => "sedan" 
  case Car("GM", "Denali") => "suv" 
  case Car("Mercedes", "300") => "luxury" 
  case Car("Buick", "LeSabre") => "sedan" 
  case _ => "Car: is of unknown type" 
} 
val typeOfBuick = carType(buickLeSabre) 

我们定义一个模式match块(如本章前一部分所述)。在match块中,我们查看一个Car对象,它的brandGMmodelDenali,等等。对于每个感兴趣的模型,我们对其类型进行分类。我们还在末尾使用了catchall _,这样我们就可以捕捉到意外的值。

我们可以在 Jupyter 中练习case类,如下截图所示:

我们定义并使用了case类作为Car。然后我们使用Car类进行了模式匹配。

Scala 不变性

不变性意味着你不能改变某些东西。在 Scala 中,除非特别标记,否则所有变量都是不可变的。这与像 Java 这样的语言相反,在这些语言中,所有变量默认都是可变的,除非特别标记为不可变。

在 Java 中,我们可以有如下的函数:

public void calculate(integer amount) { 
} 

我们可以在calculate函数内部修改amount的值。如果我们使用final关键字,我们可以告诉 Java 不允许更改该值,如下所示:

public void calculate(final integer amount) { 
} 

而在 Scala 中:

def calculate (amount: Int): Int = {  
        amount = amount + 1; 
        return amount; 
} 
var balance = 100
val result = calculate(balance)

一个类似的例程将amount变量的值保持在例程调用前的状态:

我们可以在显示中看到,即使balance是一个变量(标记为var),Scala 也不会允许你在函数内部更改它的值。传递给calculate函数的amount参数被认为是一个val,一旦初始化后就不能更改。

Scala 集合

在 Scala 中,集合根据你的使用方式自动是mutableimmutablescala.collections.immutable中的所有集合都是immutable。反之亦然,对于scala.collections.mutable。默认情况下,Scala 选择immutable集合,因此你的代码将自动使用mutable集合,如下所示:

var List mylist; 

或者,你可以在变量前加上immutable

var mylist immutable.List; 

我们可以在这个简短的示例中看到这一点:

var mutableList = List(1, 2, 3); 
var immutableList = scala.collection.immutable.List(4, 5, 6); 
mutableList.updated(1,400); 
immutableList.updated(1,700); 

正如我们在这个 Notebook 中看到的:

注意,Scala 在这里有一点小伎俩:当我们更新immutableList时,它创建了一个新的collection,正如你可以看到的变量名real_3所示。

命名参数

Scala 允许你通过名称而不是仅仅通过位置来指定参数赋值。例如,我们可以有如下代码:

def divide(dividend:Int, divisor:Int): Float =  
{ dividend.toFloat / divisor.toFloat } 
divide(40, 5) 
divide(divisor = 40, dividend = 5) 

如果我们在 Notebook 中运行这个代码,我们可以看到结果:

第一次调用通过位置划分传递的参数。第二次调用通过指定参数,而不是使用标准的序号位置分配。

Scala 特性

Scala 中的trait函数定义了一组可以被类实现的特性。trait接口类似于 Java 中的接口。

trait函数可以部分实现,强制trait的使用者(类)来实现具体的细节。

举个例子,我们可以有如下代码:

trait Color {
 def isRed(): Boolean
}
class Red extends Color {
 def isRed() = true
}
class Blue extends Color {
 def isRed() = false
}
var red = new Red();
var blue = new Blue();
red.isRed()
blue.isRed() 

该代码创建了一个名为Colortrait,并包含一个部分实现的函数isRed。因此,任何使用Color的类都必须实现isRed()

然后我们实现两个类,RedBlue,它们继承Color特性(这是使用trait的 Scala 语法)。由于isRed()函数是部分实现的,因此两个类都必须提供trait函数的实现。

我们可以在以下 Notebook 显示中看到这如何操作:

我们在输出部分(底部)看到,traitclass被创建,并且创建了两个实例,同时显示了调用这两个类的trait函数的结果。

总结

在本章中,我们为 Jupyter 安装了 Scala。我们使用 Scala 编写代码来访问更大的数据集。我们展示了 Scala 如何操作数组。我们在 Scala 中生成了随机数。还展示了高阶函数和模式匹配的例子。我们使用了 case 类。我们看到了 Scala 中不变性的例子。我们使用 Scala 包构建了集合。最后,我们了解了 Scala 特质。

在下一章中,我们将学习如何在 Jupyter 中使用大数据。

第八章:Jupyter 与大数据

大数据是每个人都在关注的话题。我认为在 Jupyter 中看看大数据能做什么会很有意思。一个处理大数据集的新兴语言是 Spark。Spark 是一个开源工具集,我们可以像使用其他语言一样在 Jupyter 中使用 Spark 代码。

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

  • 在 Jupyter 中安装 Spark

  • 使用 Spark 的功能

Apache Spark

我们将使用的工具之一是 Apache Spark。Spark 是一个用于集群计算的开源工具集。虽然我们不会使用集群,但 Spark 的典型用途是将多个机器或集群并行操作,用于分析大数据集。安装说明可以在www.dataquest.io/blog/pyspark-installation-guide找到。

在 macOS 上安装 Spark

安装 Spark 的最新说明可以在medium.freecodecamp.org/installing-scala-and-apache-spark-on-mac-os-837ae57d283f找到。主要步骤如下:

  1. brew.sh获取 Homebrew。如果你在 macOS 上进行软件开发,你很可能已经安装了 Homebrew。

  2. 安装xcode-selectxcode-select用于不同的编程语言。在 Spark 中,我们使用 Java、Scala,当然还有 Spark,具体如下:

xcode-select -install 

同样,很可能你已经为其他软件开发任务安装了此项工具。

  1. 使用 Homebrew 安装 Java:
brew cask install java 
  1. 使用 Homebrew 安装 Scala:
brew install scala 
  1. 使用 Homebrew 安装 Apache Spark:
brew install apache-spark 
  1. 你应该使用 Spark shell 测试此功能是否正常,像这样运行命令:
spark-shell 
  1. 这将显示熟悉的 Logo:
Welcome to 
 ____              __ 
 / __/__  ___ _____/ /__ 
 _\ \/ _ \/ _ `/ __/  '_/ 
 /__ / .__/\_,_/_/ /_/\_\   version 2.0.0 
 /_/ 

Using Python version 2.7.12 (default, Jul  2 2016 17:43:17) 
SparkSession available as 'spark'. 
>>> 

该网站继续讨论设置导出等,但我并没有需要做这些。

此时,我们可以启动一个 Python 3 笔记本并开始在 Jupyter 中使用 Spark。

你可以输入quit()退出。

现在,当我们在使用 Python 内核时运行笔记本时,我们可以访问 Spark。

Windows 安装

我以前在 Jupyter 中使用 Python 2 运行过 Spark。对于 Windows,我无法正确安装。

第一个 Spark 脚本

我们的第一个脚本读取一个文本文件,并查看每行的长度总和,如下所示。请注意,我们读取的是当前运行的笔记本文件;该笔记本名为Spark File Lengths,并存储在Spark File Lengths.ipynb文件中:

import pyspark
if not 'sc' in globals():
    sc = pyspark.SparkContext()
lines = sc.textFile("Spark File Line Lengths.ipynb")
lineLengths = lines.map(lambda s: len(s))
totalLengths = lineLengths.reduce(lambda a, b: a + b)
print(totalLengths)

print(totalLengths)脚本中,我们首先初始化 Spark,但仅当我们尚未初始化它时才会进行此操作。如果你尝试多次初始化 Spark,它会报错,因此所有 Spark 脚本都应该有这个if语句前缀。

该脚本读取一个文本文件(此脚本的源文件),处理每一行,计算其长度,然后将所有长度相加。

lambda函数是一个匿名(无名)函数,它接受参数并返回一个值。在第一个例子中,给定一个s字符串,返回其长度。

reduce函数将每个值作为参数,应用第二个参数,并用结果替换第一个值,然后继续处理剩余的列表。在我们的案例中,它遍历行的长度并将它们加总。

然后,在 Notebook 中运行时,我们看到如下截图。请注意,你的文件大小可能略有不同。

此外,当你第一次启动 Spark 引擎(使用sc = pyspark.SparkContext()这一行代码)时,可能需要一些时间,且脚本可能无法成功完成。如果发生这种情况,请再试一次:

Spark 单词计数

既然我们已经看到了一些功能,接下来让我们进一步探索。我们可以使用类似以下的脚本来统计文件中单词的出现次数:

import pyspark
if not 'sc' in globals():
    sc = pyspark.SparkContext()

#load in the file
text_file = sc.textFile("Spark File Words.ipynb")

#split file into distinct words
counts = text_file.flatMap(lambda line: line.split(" ")) \
    .map(lambda word: (word, 1)) \
    .reduceByKey(lambda a, b: a + b)

# print out words found
for x in counts.collect():
    print(x)

我们对编码有相同的引言。然后,我们将文本文件加载到内存中。

一旦文件加载完成,我们将每行拆分成单词,并使用lambda函数统计每个单词的出现次数。代码实际上为每个单词出现创建了一个新记录,比如"at"出现一次。这个过程可以分配到多个处理器,每个处理器生成这些低级别的信息位。我们并不关注优化这个过程。

一旦我们拥有了所有这些记录,我们就根据单词出现的次数来减少/总结记录集。

counts对象在 Spark 中称为弹性分布式数据集RDD)。它是弹性的,因为我们在处理数据集时会确保持久化。RDD 是分布式的,因为它可以被操作集群中的所有节点所处理。当然,它是由各种数据项组成的数据集。

最后的for循环对 RDD 执行collect()操作。如前所述,RDD 可能分布在多个节点上。collect()函数将 RDD 的所有副本拉取到一个位置。然后,我们遍历每条记录。

当我们在 Jupyter 中运行时,我们看到类似于以下的输出:

列表略有缩略,因为单词列表会继续一段时间。值得注意的是,Spark 中的单词拆分逻辑似乎并不十分有效;一些结果并不是单词,比如第一个条目是空字符串。

排序后的单词计数

使用相同的脚本,稍微修改一下,我们可以再进行一次调用并对结果进行排序。现在脚本如下所示:

import pyspark
if not 'sc' in globals():
    sc = pyspark.SparkContext()

#load in the file
text_file = sc.textFile("Spark Sort Words from File.ipynb")

#split file into sorted, distinct words
sorted_counts = text_file.flatMap(lambda line: line.split(" ")) \
    .map(lambda word: (word, 1)) \
    .reduceByKey(lambda a, b: a + b) \
    .sortByKey()

# print out words found (in sorted order)
for x in sorted_counts.collect():
    print(x)

在这里,我们添加了另一个 RDD 创建函数调用,sortByKey()。因此,在我们完成映射/减少操作并得到了单词和出现次数的列表之后,我们可以轻松地对结果进行排序。

结果输出如下所示:

估算π值

如果我们有如下代码,我们可以使用mapreduce来估算π值:

import pyspark 
import random 
if not 'sc' in globals(): 
    sc = pyspark.SparkContext() 

NUM_SAMPLES = 10000 
random.seed(113) 

def sample(p): 
    x, y = random.random(), random.random() 
    return 1 if x*x + y*y < 1 else 0 

count = sc.parallelize(range(0, NUM_SAMPLES)) \ 
    .map(sample) \ 
    .reduce(lambda a, b: a + b) 

print("Pi is roughly %f" % (4.0 * count / NUM_SAMPLES)) 

这段代码有相同的引言。我们正在使用 Python 的random包,并设定了样本的数量常量。

我们正在构建一个称为count的 RDD。我们调用parallelize函数来在可用的节点之间分配这个过程。代码只是映射sample函数调用的结果。最后,我们通过将生成的映射集合减少来得到总数。

sample函数获取两个随机数,并根据它们的大小返回 1 或 0。我们正在寻找一个小范围内的随机数,然后检查它们是否落在同一直径的圆内。足够大的样本,我们将得到PI(3.141...)

如果我们在 Jupyter 中运行,我们会看到以下内容:

当我使用NUM_SAMPLES = 100000运行时,最终得到了PI = 3.126400

日志文件检查

我从monitorware.com下载了一个access_log文件。和任何其他的网页访问日志一样,我们每条记录一行,就像这样:

64.242.88.10 - - [07/Mar/2004:16:05:49 -0800] "GET /twiki/bin/edit/Main/Double_bounce_sender?topicparent=Main.ConfigurationVariables HTTP/1.1" 401 12846 

第一部分是调用者的 IP 地址,然后是时间戳,HTTP 访问类型,引用的 URL,HTTP 类型,生成的 HTTP 响应代码,最后是响应中的字节数。

我们可以使用 Spark 加载和解析日志条目的一些统计信息,就像这个脚本一样:

import pyspark
if not 'sc' in globals():
    sc = pyspark.SparkContext()

textFile = sc.textFile("access_log")
print(textFile.count(), "access records")

gets = textFile.filter(lambda line: "GET" in line)
print(gets.count(), "GETs")

posts = textFile.filter(lambda line: "POST" in line)
print(posts.count(), "POSTs")

other = textFile.subtract(gets).subtract(posts)
print(other.count(), "Other")

#curious what Other requests may have been
for x in other.collect():
    print(x)

这个脚本与其他脚本有相同的序言。我们读取access_log文件。然后,我们打印count记录。

类似地,我们了解到有多少日志条目是GETPOST操作。GET被认为是最普遍的。

当我第一次这样做时,我真的不希望有任何其他情况发生,所以我从集合中移除了getsposts,并打印出离群值,看看它们是什么。

当我们在 Jupyter 中运行时,我们看到了预期的输出:

文本处理速度不是很快(特别是对于如此少量的记录)。

我喜欢能够以这样的方式处理数据框架。以程序化的方式进行基本代数运算,而不必担心边界情况,这确实令人愉悦。

顺便说一句,HEAD请求的工作方式与GET完全相同,但不返回HTTP正文。这允许调用者确定应该返回什么类型的响应,并做出相应的响应。

Spark 质数

我们可以运行一系列数字通过一个过滤器来确定每个数字是否是质数或不是。我们可以使用这个脚本:

import pyspark
if not 'sc' in globals():
    sc = pyspark.SparkContext()

def is_it_prime(number):

    #make sure n is a positive integer
    number = abs(number)

    #simple tests
    if number < 2:
        return False

    #2 is special case
    if number == 2:
        return True

    #all other even numbers are not prime
    if not number & 1:
        return False

    #divisible into it's square root
    for x in range(3, int(number**0.5)+1, 2):
        if number % x == 0:
            return False

    #must be a prime
    return True

# pick a good range
numbers = sc.parallelize(range(100000))

# see how many primes are in that range
print(numbers.filter(is_it_prime).count())

这个脚本生成了最多100000的数字。

然后,我们循环遍历每个数字,并将其传递给我们的过滤器。如果过滤器返回True,我们就得到一条记录。然后,我们只需计算找到的结果数量。

在 Jupyter 中运行时,我们看到以下内容:

这个速度非常快。我在等待中没有注意到它如此迅速。

Spark 文本文件分析

在这个示例中,我们将浏览一篇新闻文章,以确定文章的一些基本信息。

我们将会使用以下脚本对来自www.newsitem.com的 2600 条新闻文章进行处理:

import pyspark
if not 'sc' in globals():
    sc = pyspark.SparkContext()

#pull out sentences from article
sentences = sc.textFile('2600raid.txt') \
    .glom() \
    .map(lambda x: " ".join(x)) \
    .flatMap(lambda x: x.split("."))
print(sentences.count(),"sentences")

#find the bigrams in the sentences
bigrams = sentences.map(lambda x:x.split()) \
    .flatMap(lambda x: [((x[i],x[i+1]),1) for i in range(0, len(x)-1)])
print(bigrams.count(),"bigrams")

#find the (10) most common bigrams
frequent_bigrams = bigrams.reduceByKey(lambda x,y:x+y) \
    .map(lambda x:(x[1], x[0])) \
    .sortByKey(False)
frequent_bigrams.take(10)

代码读取文章并根据句点的出现将其拆分成sentences。然后,代码映射出出现的bigrams。bigram 是一对相邻的单词。接着,我们对列表进行排序,打印出前十个最常见的词对。

当我们在 Notebook 中运行这个时,看到以下结果:

我完全不知道从输出中该期待什么。很奇怪的是,你可以从文章中获取一些洞察力,因为themall出现了15次,而theguards出现了11次——应该是发生了一次袭击,地点是在商场,且某种程度上涉及了保安。

Spark 正在评估历史数据

在这个例子中,我们结合前面几个部分,查看一些历史数据并确定一些有用的属性。

我们使用的历史数据是 Jon Stewart 电视节目的嘉宾名单。数据中的典型记录如下所示:

1999,actor,1/11/99,Acting,Michael J. Fox 

这包含了年份、嘉宾的职业、出现日期、职业的逻辑分组和嘉宾的姓名。

对于我们的分析,我们将查看每年出现的次数、最常出现的职业以及最常出现的人物。

我们将使用这个脚本:

#Spark Daily Show Guests
import pyspark
import csv
import operator
import itertools
import collections

if not 'sc' in globals():
 sc = pyspark.SparkContext()

years = {}
occupations = {}
guests = {}

#file header contains column descriptors:
#YEAR, GoogleKnowledge_Occupation, Show, Group, Raw_Guest_List

with open('daily_show_guests.csv', 'rt', errors = 'ignore') as csvfile: 
 reader = csv.DictReader(csvfile)
 for row in reader:
 year = row['YEAR']
 if year in years:
 years[year] = years[year] + 1
 else:
 years[year] = 1

 occupation = row['GoogleKnowlege_Occupation']
 if occupation in occupations:
 occupations[occupation] = occupations[occupation] + 1
 else:
 occupations[occupation] = 1

 guest = row['Raw_Guest_List']
 if guest in guests:
 guests[guest] = guests[guest] + 1
 else:
 guests[guest] = 1

#sort for higher occurrence
syears = sorted(years.items(), key = operator.itemgetter(1), reverse = True)
soccupations = sorted(occupations.items(), key = operator.itemgetter(1), reverse = True)
sguests = sorted(guests.items(), key = operator.itemgetter(1), reverse = True)

#print out top 5's
print(syears[:5])
print(soccupations[:5])
print(sguests[:5]) 

该脚本有一些特点:

  • 我们使用了几个包。

  • 它有熟悉的上下文序言。

  • 我们为yearsoccupationsguests启动字典。字典包含key和值。对于此用例,键将是来自 CSV 的原始值,值将是数据集中该项出现的次数。

  • 我们打开文件并开始逐行读取,使用reader对象。由于文件中有一些空值,我们在读取时忽略错误。

  • 在每一行中,我们获取感兴趣的值(yearoccupationname)。

  • 我们查看该值是否存在于相应的字典中。

  • 如果存在,递增该值(计数器)。

  • 否则,初始化字典中的一个条目。

  • 然后我们按照每个项目出现次数的逆序对字典进行排序。

  • 最后,我们展示每个字典的前五个值。

如果我们在 Notebook 中运行这个,我们会得到以下输出:

我们展示脚本的尾部和之前的输出。

可能有更聪明的方法来做这些事情,但我并不清楚。

累加器的构建方式相当标准,无论你使用什么语言。我认为这里有机会使用map()函数。

我真的很喜欢只需要轻松去除列表/数组,而不需要调用函数。

每年嘉宾的数量非常一致。演员占据主导地位——他们大概是观众最感兴趣的群体。嘉宾名单有点令人惊讶。嘉宾大多数是演员,但我认为所有人都有强烈的政治倾向。

总结

在本章中,我们通过 Python 编程在 Jupyter 中使用了 Spark 功能。首先,我们安装了 Spark 插件到 Jupyter。我们编写了一个初始脚本,它只是从文本文件中读取行。我们进一步分析了该文件中的单词计数,并对结果进行了排序。我们编写了一个脚本来估算 pi 值。我们评估了网页日志文件中的异常。我们确定了一组素数,并对文本流进行了某些特征的评估。

第九章:交互式小部件

Jupyter 有一个机制可以在脚本运行时收集用户的输入。为了实现这一点,我们在脚本中使用小部件或用户界面控制进行编码。本章中我们将使用的小部件定义在ipywidgets.readthedocs.io/en/latest/

例如,有以下小部件:

  • 文本输入:Notebook 用户输入一个字符串,该字符串将在后续脚本中使用。

  • 按钮点击:这些通过按钮向用户展示多个选项。然后,依据选择的按钮(点击的按钮),你的脚本可以根据用户选择的按钮改变方向。

  • 滑块:你可以为用户提供一个滑块,让用户在你指定的范围内选择一个值,然后你的脚本可以根据这个值进行相应的操作。

  • 切换框和复选框:这些让用户选择他们有兴趣操作的脚本选项。

  • 进度条:进度条可以用来显示他们在多步骤过程中已完成的进度。

在某些情况下,这个机制可以完全开放,因为底层的从用户收集输入通常是可用的。因此,你可以制作出不符合标准用户输入控制范式的非常有趣的小部件。例如,有一个小部件允许用户点击地理地图以发现数据。

在本章中,我们将讨论以下主题:

  • 安装小部件

  • 小部件基础

  • Interact 小部件

  • 交互式小部件

  • 小部件包

安装小部件

  • 小部件包是对标准 Jupyter 安装的升级。你可以使用这个命令来更新小部件包:
pip install ipywidgets
  • 完成后,你必须使用这个命令来升级你的 Jupyter 安装:
jupyter nbextension enable --py widgetsnbextension
  • 然后你必须使用这个命令:
conda update jupyter_core jupyter_client
  • 我们整理了一个基本示例小部件笔记本,以确保一切正常工作:
 #import our libraries 
from ipywidgets import * 
from IPython.display import display 

#create a slider and message box 
slider = widgets.FloatSlider() 
message = widgets.Text(value = 'Hello World') 

#add them to the container 
container = widgets.Box(children = (slider, message)) 
container.layout.border = '1px black solid' 

display(container) 
  • 最终我们得到了以下截图,其中显示了包含滑块和消息框的容器小部件:

小部件基础

所有小部件通常以相同的方式工作:

  • 你可以创建或定义一个小部件实例。

  • 你可以预设小部件的属性,比如它的初始值或要显示的标签。

  • 小部件可以对用户的不同输入做出反应。这些输入是通过一个处理器或 Python 函数收集的,当用户对小部件执行某些操作时,函数就会被调用。例如,当用户点击按钮时,调用处理器。

  • 小部件的值可以像任何其他变量一样在脚本中使用。例如,你可以使用一个小部件来确定绘制多少个圆。

Interact 小部件

Interact 是一个基本的小部件,似乎用于衍生所有其他小部件。它具有可变参数,依赖于参数的不同,将呈现不同种类的用户输入控制。

Interact 小部件滑动条

我们可以使用 interact 通过传入一个范围来生成一个滑动条。例如,以下是我们的脚本:

#imports 
from ipywidgets import interact 

# define a function to work with (cubes the number) 
def myfunction(arg): 
    return arg+1 

#take values from slidebar and apply to function provided 
interact(myfunction, arg=9); 

请注意,interact函数调用后的分号是必须的。

我们有一个脚本,它会执行以下操作:

  • 引用我们要使用的包

  • 定义一个函数,每次用户输入一个值时都会调用此函数。

  • 调用interact,传递我们的处理函数和一系列值

当我们运行此脚本时,我们会得到一个用户可修改的滚动条:

用户可以通过滑动垂直条在值的范围内滑动。上限为 27,下限为-1(假设我们可以向interact传递额外的参数来设置可选择的值范围)。每次interact小部件中的值更改时,myfunction都会被调用,并打印结果。因此,我们看到选中的是 27,显示的是数字 28(经过myfunction处理后 - 27 + 1)。

交互式复选框控件

我们可以根据传递给interact的参数来更改生成的控件类型。如果我们有以下脚本:

from ipywidgets import interact
def myfunction(x):
    return x
# the second argument defines which of the interact widgets to use
interact(myfunction, x=False);

我们正在执行与之前相同的步骤;然而,传递的值是False(但它也可以是True)。interact函数检查传入的参数,判断它是布尔值,并为布尔值提供适当的控件:复选框。

如果我们在 Notebook 中运行上面的脚本,我们将得到如下显示:

交互式文本框控件

我们可以通过传递不同的参数给interact来生成文本输入控件。例如,参见以下脚本:

from ipywidgets import interact
def myfunction(x):
    return x
#since argument is a text string, interact generates a textbox control for it
interact(myfunction, x= "Hello World");

这会生成一个初始值为Hello World的文本输入控件:

交互式下拉菜单

我们还可以使用interact函数为用户生成一个下拉列表框,让他们选择。例如,在下面的脚本中,我们生成一个包含两个选项的下拉菜单:

from ipywidgets import interact 
def myfunction(x): 
    return x 
interact(myfunction, x=['red','green']); 

这个脚本将执行以下操作:

  • 引入interact引用,

  • 定义一个函数,每当用户更改控件值时都会调用该函数

  • 调用interact函数并传入一组值,interact将解释这些值,意味着为用户创建一个下拉菜单。

如果我们在 Notebook 中运行此脚本,我们将得到以下显示:

上面的截图显示,我们在底部打印的值将根据用户在下拉菜单中选择的内容而变化。

交互式小部件

还有一个交互式小部件。交互式小部件类似于interact小部件,但不会在脚本直接调用时显示用户输入控件。如果你有一些需要计算的参数,或者想在运行时决定是否需要显示控件,这会非常有用。

例如,我们可以有一个类似于之前脚本的脚本,如下所示:

from ipywidgets import interactive
from IPython.display import display
def myfunction(x):
return x
w = interactive(myfunction, x= "Hello World ");
display(w)

我们对脚本做了几个更改:

  • 我们正在引用交互式小部件

  • 交互式函数返回的是一个控件,而不是立即显示一个值。

  • 我们必须自己编写显示控件的脚本

如果我们运行以下脚本,用户看到的效果非常相似:

控件

还有另一个控件包,叫做widgets,它包含了你可能想要使用的所有标准控件,提供了许多可选参数,允许你自定义显示效果。

进度条控件

这个包中有一个控件可以向用户显示进度条。我们可以使用以下脚本:

import ipywidgets as widgets 
widgets.FloatProgress( 
    value=45, 
    min=0, 
    max=100, 
    step=5, 
    description='Percent:', 
) 

上面的脚本将会如下所示显示我们的进度条:

我们看到一个进度条,看起来大约是 45%。

列表框控件

我们也可以使用listbox控件,称为Dropdown,如下脚本所示:

import ipywidgets as widgets 
from IPython.display import display 
w = widgets.Dropdown( 
    options={'Pen': 7732, 'Pencil': 102, 'Pad': 33331}, 
    description='Item:', 
) 
display(w) 
w.value 

这个脚本会向用户显示一个包含PenPencilPad值的列表框。当用户选择其中一个值时,相关的值会返回并存储在w变量中,我们会像以下截图那样显示它:

因此,我们看到了与笔相关的库存值。

文本控件

text控件从用户那里收集一个文本字符串,供脚本中的其他地方重用。文本控件有一个描述(标签)和一个值(由用户输入或在脚本中预设)。

在这个示例中,我们只会收集一个文本字符串,并将其作为脚本输出的一部分显示在屏幕上。我们将使用以下脚本:

from ipywidgets import widgets
from IPython.display import display
#take the text from the box, define variable for handler
text = widgets.Text()
#display it
display(text)
def handle_submit(sender):
    print(text.value)
#when we hit return in the textbox call upon the handler
text.on_submit(handle_submit)

包含基本控件的 Python 包是ipywidgets,所以我们需要引用它。我们为文本字段定义了一个处理程序,当用户输入文本值并点击提交时,该处理程序会被调用。在这里,我们使用了text控件。

如果我们在 Notebook 中运行上面的脚本,显示效果如下:

我们应该指出本页的一些亮点:

  • 页面元素的排列顺序很重要。处理程序引用的文本字段必须在引用之前定义。

  • 当调用控件时,控件会自动寻找与脚本相关联的处理程序。在这种情况下,我们有一个submit处理程序。还有许多其他处理程序可以使用。text.on_submit将处理程序分配给控件。

  • 否则,我们将得到一个标准的 Python Notebook。

  • 如果我们运行脚本(单元格 | 运行全部),我们将得到上面的截图(等待我们在文本框中输入值):

  • 所以,我们的脚本已经设置了一个控件,用于收集用户的输入,之后对这个值进行处理。(我们这里只是显示它,但我们也可以使用这个输入进行进一步处理。)

按钮控件

类似地,我们也可以在脚本中使用一个Button控件,如下例所示:

from ipywidgets import widgets 
from IPython.display import display 

button = widgets.Button(description="Submit"); 
display(button) 

def on_button_clicked(widget): 
    print("Clicked Button:" + widget.description); 

button.on_click(on_button_clicked); 

这个脚本的功能如下:

  • 引用我们想要使用的widgets包中的功能。

  • 创建我们的按钮。

  • 定义了一个按钮点击事件的处理程序。该处理程序接收被点击的Button对象(小部件)。

  • 该处理程序显示了关于点击按钮的信息(你可以想象,如果我们有多个按钮在同一界面上,我们会希望确定是哪个按钮被点击了)。

  • 最后,我们将定义的处理程序分配给我们创建的Button对象。

请注意处理程序代码的缩进;这是标准的 Python 风格,必须遵循。

如果我们在 Notebook 中运行前面的脚本,我们会看到如下截图:

请注意以下图片底部的Submit按钮。你可以更改按钮的其他属性,例如位置、大小、颜色等。

如果我们点击Submit按钮,则会显示以下界面,显示我们关于按钮被点击的消息:

小部件属性

所有小部件控件都有一组可调整的显示属性。你可以通过获取一个控件的实例,并在 Notebook 中运行control.keys命令来查看属性列表,如下例所示:

from ipywidgets import * 
w = IntSlider() 
w.keys 

这个脚本引入了所有小部件控件的一个通用引用。接着,我们创建一个IntSlider实例,并显示可以调整的属性列表。最终,我们得到如下所示的界面:

如你所见,这个列表非常全面:

属性 描述
orientation 是否左对齐、右对齐或居中对齐
color 字体颜色
height 控件的高度
disabled 控件是否被禁用
visible 控件是否显示?
font_style 字体样式,例如,斜体
min 最小值(在范围列表中使用)
background_color 控件的背景颜色
width 控件的宽度
font_family 控件中文本使用的字体系列
description 描述字段用于文档说明
max 最大值(范围内)
padding 应用的内边距(控件边缘)
font_weight 控件中使用的字体粗细,例如,粗体
font_size 控件中文本的字体大小
value 控件选定和输入的值
margin 显示控件时使用的边距

调整小部件属性

我们可以在脚本中调整这些属性,例如使用以下脚本来禁用文本框(文本框仍然会显示,但用户不能输入任何值)。我们可以使用以下脚本:

from ipywidgets import * 
Text(value='You can not change this text!', disabled=True) 

这是前面代码的截图:

当一个字段被禁用时,文本框会变灰。当用户将光标悬停在该字段上时,会出现一个带有斜线的红色圆圈,表示该字段不能被更改。

调整属性

所有之前显示的属性都可以读写。我们可以通过一个小脚本来展示这个过渡,脚本如下:

from ipywidgets import * 
w = IntSlider() 
original = w.value 
w.value = 5 
original, w.value 

该脚本创建一个滑块,获取其当前值,将值更改为 5,然后显示控件的原始值和当前值。

如果我们在 Notebook 中运行前面的脚本,我们将看到以下预期的结果:

控件事件

所有控件都通过响应用户的操作来工作,无论是鼠标还是键盘。控件的默认操作是内置在软件中的,但你可以添加自己的事件处理(用户操作)。

我们之前见过这种事件处理(例如,在滑块的部分,每当用户改变滑块值时,都会调用一个函数)。但是,让我们更深入地探讨一下。

我们可以有以下脚本:

from ipywidgets import widgets
from IPython.display import display
button = widgets.Button(description="Click Me!")
display(button)
def on_button_clicked(b):
    print("Button clicked.")
button.on_click(on_button_clicked)

这个脚本执行了以下操作:

  • 创建一个按钮。

  • 向用户显示按钮。

  • 定义了一个事件处理程序的点击事件。它打印出你点击了屏幕上的消息。你可以在处理程序中写入任何你想要的 Python 语句。

  • 最后,我们将点击事件处理程序与我们创建的按钮关联。所以,现在当用户点击我们的按钮时,事件处理程序被调用,并且 Button clicked 消息显示在屏幕上(如下面的截图所示):

如果我们在 Notebook 中运行上面的脚本,并点击按钮几次,我们将得到以下显示:

控件容器

你也可以直接使用 Python 语法通过将子元素传递给构造函数来组合控件容器。例如,我们可以有以下脚本:

 #import our libraries 
from ipywidgets import * 
from IPython.display import display 

#create a slider and message box 
slider = widgets.FloatSlider() 
message = widgets.Text(value = 'Hello World') 

#add them to the container 
container = widgets.Box(children = (slider, message)) 
container.layout.border = '1px black solid' 

display(container) 

上面的脚本显示了我们正在创建一个容器(这是一个 Box 控件),在其中我们指定了子控件。调用显示容器的命令时,也会依次显示子元素。所以,我们最终会得到如下截图所示的显示:

你可以看到框周围的边框和框中的两个控件。

类似地,我们也可以在容器显示后,使用如下语法将子元素添加到容器中:

from ipywidgets import * 
from IPython.display import display 
container = widgets.Box() 
container.layout.border = '1px black solid' 
display(container) 
slider = widgets.FloatSlider() 
message = widgets.Text(value='Hello World') 
container.children=[slider, message] 

当我们将子元素添加到容器时,容器会重新绘制,这会导致所有子元素的重新绘制。

如果我们在另一个 Notebook 中运行这个脚本,我们将得到一个与之前示例非常相似的结果,显示如下截图所示:

总结

在本章中,我们将控件添加到了我们的 Jupyter 安装中,并使用了 interact 和 interactive 控件来产生各种用户输入控件。然后,我们深入研究了控件包,调查了可用的用户控件、容器中的可用属性、从控件发出的事件以及如何为控件构建容器。

在下一章中,我们将探讨共享笔记本并将其转换为不同格式。

第十章:分享与转换 Jupyter 笔记本

一旦你开发好了笔记本,你就可能希望与其他人分享。我们将在本章中介绍一个典型的分享机制——将你的笔记本放置在一个可访问的互联网上的服务器上。

当你将一个笔记本提供给另一个人时,他们可能需要以不同的格式接收笔记本,以满足他们的系统要求。我们还将介绍一些机制,帮助你将笔记本以不同格式提供给他人。

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

  • 分享笔记本

  • 转换笔记本

分享笔记本

分享笔记本的典型机制是将笔记本放置在一个网站上。网站运行在一个服务器或分配的机器空间上。服务器处理所有与运行网站相关的事务,例如跟踪多个用户并进行用户登录和注销。

然而,为了使笔记本能够被使用,网站必须安装笔记本逻辑。一个典型的网站知道如何根据一些源文件将内容作为 HTML 交付。最基本的形式是纯 HTML,其中你在网站上访问的每一页都与 Web 服务器上的一个 HTML 文件完全对应。其他语言也可以用来开发网站(例如 Java 或 PHP),因此服务器需要知道如何从这些源文件中访问所需的 HTML。在我们的上下文中,服务器需要知道如何访问你的笔记本,以便将 HTML 交付给用户。

即使笔记本仅在你的本地机器上运行,它们也是通过浏览器访问你的本地机器(服务器),而不是通过互联网——因此,Web、HTML 和互联网访问已经被提供。

如果笔记本位于一个真实的网站上,那么所有能够访问该网站的人都可以使用它——无论服务器是在你办公室环境中的本地网络上运行,还是可以通过互联网供所有用户访问。

你始终可以为网站增加安全措施,以确保只有那些你授权的用户才能使用你的笔记本。安全机制取决于所使用的 Web 服务器软件类型。

在笔记本服务器上分享笔记本

Jupyter 过程内置了将笔记本暴露为其自身 Web 服务器的功能。假设服务器是一个其他用户可以访问的机器,你可以配置 Jupyter 在该服务器上运行。你必须向 Jupyter 提供配置信息,以便它知道如何进行。生成 Jupyter 安装配置文件的命令如下所示:

请注意,由于我们是在 Anaconda 中运行,我正在从该目录中运行命令:

C:\Users\Dan\Anaconda3\Scripts>jupyter notebook --generate-config

默认的config文件被写入:C:\Users\Dan\.jupyter\jupyter_notebook_config.py

此命令将在你的~./jupyter目录中生成一个jupyter_notebook_config.py文件。对于 Microsoft 用户,该目录是你主目录下的一个子目录。

配置文件包含了您可以用来将笔记本暴露为服务器的设置:

c.NotebookApp.certfile = u'/path/to/your/cert/cert.pem'
c.NotebookApp.keyfile = u'/ path/to/your/cert/key.key'
c.NotebookApp.ip = '*'
c.NotebookApp.password = u'hashed-password'
c.NotebookApp.open_browser = False
c.NotebookApp.port = 8888

文件中的设置在下表中进行了说明:

设置 描述
c.NotebookApp.certfile 这是您网站证书的文件路径。如果您有 SSL 证书,您需要更改该设置以指向文件的位置。该文件可能不是 .PEM 扩展名的文件。SSL 证书有多种格式。
c.NotebookApp.keyfile 这是访问您网站证书密钥位置的路径。与其直接指定证书密钥,您应该将密钥存储在一个文件中。所以,如果您想为您的笔记本应用 SSL 证书,您需要指定该文件的位置。密钥通常是一个非常大的十六进制数字,因此它被存储在自己的文件中。此外,将其存储在文件中提供了额外的保护,因为密钥存储所在的目录通常有有限的访问权限。
c.NotebookApp.ip 机器的 IP 地址。使用通配符 * 来开放给所有人。这里,我们指定了访问笔记本网站的机器的 IP 地址。
c.NotebookApp.password 哈希密码——用户访问您的笔记本时需要提供密码,以响应标准的登录验证。
c.NotebookApp.open_browser True/False——启动笔记本服务器时是否打开浏览器窗口?
c.NotebookApp.port 用于访问您的服务器的端口;它应该对机器开放。

每个网站在较低层次上通过 IP 地址进行定位。IP 地址是一个四部分的数字,用于标识所涉及服务器的位置。IP 地址可能类似于 172.32.88.7

网络浏览器与互联网软件协同工作,知道如何使用 IP 地址来定位目标服务器。该软件套件也知道如何将您在浏览器中提到的 URL(例如 www.microsoft.com)转化为 IP 地址。

一旦您适当地更改了设置,您应该能够在浏览器中访问配置的 URL 来访问您的笔记本。该 URL 将是 HTTP 或 HTTPS(取决于是否应用了 SSL 证书)、IP 地址和端口的组合,例如 HTTPS://123.45.56.9:8888

在笔记本服务器上共享加密的笔记本

如果您有 SSL 证书可以应用,之前展示的两项设置可以使用。如果没有 SSL 证书,密码(如前所述)以及所有其他交互将在用户的浏览器和服务器之间以明文传输。如果您在笔记本中处理敏感信息,应该获取 SSL 证书并为您的服务器做出相应的设置更改。

如果你需要更高的安全性来保护笔记本的访问,下一步是提供一个 SSL 证书(将证书放在你的计算机上,并在配置文件中提供路径)。有许多公司提供 SSL 证书。截止目前,最便宜的是Let's encrypt,它会免费提供一个低级别的 SSL 证书。(SSL 证书有不同等级,有些是需要付费的。)

再次说明,一旦你设置了有关证书的前述设置,你就可以通过HTTPS://前缀访问你的笔记本服务器,并且可以确保用户浏览器和笔记本服务器之间的所有传输都是加密的,因此是安全的。

在 Web 服务器上共享笔记本

为了将你的笔记本添加到现有的 Web 服务器中,你需要执行前面的步骤,并在笔记本的配置文件中添加一些额外的信息,如下例所示:

c.NotebookApp.tornado_settings = {
'headers': {
'Content-Security-Policy': "frame-ancestors 'https://yourwebsite.com' 'self' "
}
}

在这里,你需要将yourwebsite.com替换为你网站的 URL。

完成后,你可以通过你的网站访问笔记本。

通过 Docker 共享笔记本

Docker 是一个开源的、轻量级的容器,用于分发软件。一个典型的 Docker 实例在机器的某个端口上运行着一个完整的 Web 服务器和一个特定的 Web 应用。Docker 实例中运行的软件的具体信息由 Dockerfile 控制。该文件向 Docker 环境提供命令,指示使用哪些组件来配置该实例。一个用于 Jupyter 实现的 Dockerfile 示例如下:

ENV TINI_VERSION v0.6.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini
RUN chmod +x /usr/bin/tini
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 8888
CMD ["jupyter", "Notebook", "--port=8888", "--no-browser", "--ip=0.0.0.0"]

以下是对每个 Dockerfile 命令的讨论:

  • ENV命令告诉 Docker 使用一个专用的操作系统。这是必要的,因为 Jupyter 存在一个缺陷,会不断地从你的计算机上获取和释放资源。

  • ADD命令告诉 Dockertini代码的位置。

  • RUN命令改变tini目录的访问权限。

  • ENTRYPOINT命令告诉 Docker 使用什么作为 Docker 实例的操作系统。

  • EXPOSE命令告诉 Docker 要在哪个端口上公开你的笔记本。

  • CMD命令告诉 Docker 在环境设置好之后运行哪些命令。CMD参数告诉你熟悉的jupyter Notebook命令,它用于在你的机器上启动 Jupyter。

一旦 Docker 实例部署到你的 Docker 机器上,你可以在指定的端口(8888)访问该 Docker 实例,例如,http://machinename.com:8888

在公共服务器上共享笔记本

当前,有一个托管公司允许你免费托管笔记本:GitHub。GitHub 是源代码管理系统(Git 源代码管理)的标准 Web 提供商。源代码管理用于保持文件的历史版本,帮助你回溯步骤。

GitHub 的实现包括了您在笔记本中使用所需的所有工具,这些工具已经安装在服务器上。例如,在前面的章节中,如果您想在笔记本中使用 R 编程,您需要在自己的计算机上安装 R 工具集。而 GitHub 已经完成了这些步骤。

访问 github.com/ 网站并注册一个免费的账户。

登录后,您将获得一个可以添加内容的网站。如果您有开发工具(git 推送命令是程序员用来将文件存储到 Git 服务器上的命令),您可以使用这些工具,或者直接将您的笔记本文件(ipynb)拖放到您的 GitHub 网站上。

我在那里创建了一个帐户,包含一个 notebooks 目录,并将其中一个 notebooks 文件放置到该网站上。我的 GitHub 网站如下所示:

您可以看到 Python Data Access.ipynb 文件位于屏幕的顶部。

如果您点击该 notebooks 文件,您将在浏览器中看到预期的笔记本运行效果。

注意:目前在 GitHub 上运行 notebooks 存在问题。之前是可以正常工作的。我预计会有修复措施来重新启用 Jupyter。

如果您回顾上一章,您会看到相同的显示(去掉了 GitHub 装饰元素)。

这个笔记本可以通过以下 URL 直接访问: github.com/danieltoomey/notebooks/blob/master/Python%20Data%20Access.ipynb。因此,您可以将您的笔记本提供给其他用户,只需将 URL 交给他们即可。

当您登录 GitHub 后,显示界面会略有不同,因为您将对 GitHub 内容有更多的控制权限。

转换笔记本

将笔记本转换为其他格式的标准工具是使用 nbconvert 工具。它已内置在您的 Jupyter 安装中。您可以直接在笔记本的用户界面中访问该工具。如果您打开一个笔记本并选择 Jupyter 文件菜单项,您将看到多个“另存为”选项:

选项包括:

格式类型 文件扩展名
Notebook .ipynb
Scala211 .scala
HTML .html
Markdown .md
reST .rst
LaTeX .tex
PDF via LaTeX .pdf

注意:由于我们使用的是 Scala 笔记本,因此第二个选项中提供的是该语言。如果我们有其他语言的笔记本,那么该语言将是选择项。

对于这些示例,如果我们从前一章节中取出一个笔记本,Jupyter 笔记本看起来是这样的:

笔记本格式

笔记本格式(.ipynb)是笔记本的原生格式。我们在前面的章节中已经查看过该文件,了解 Jupyter 在笔记本中存储了什么内容。

如果您想让其他用户完全访问您的 Notebook,可以使用 Notebook 格式,因为他们将从自己的系统运行您的 Notebook。

您也可能想这样做,以便将您的 Notebook 保存在其他介质中。

Scala 格式

Scala 格式(.scala)对应于您的 Notebook 的 Scala 实现。如果您使用 JavaScript 作为 Notebook 的语言,这将是 Notebook 页面的直接导出。

如果您使用了其他语言(例如 Python)编写 Notebook 脚本,那么“下载为”选项会相应变化,例如“下载为 | Python(.py)”。

使用我们的示例,如预期的那样,Scala 格式等同于 Jupyter 显示:

import scala.io.Source; 

//copied file locally https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data 
val filename = "iris.data" 
//println("SepalLength, SepalWidth, PetalLength, PetalWidth, Class"); 

//load iris data into an array 
val array = scala.collection.mutable.ArrayBuffer.empty[Float] 
for (line <- Source.fromFile(filename).getLines) { 
 var cols = line.split(",").map(_.trim); 
 //println(s"${cols(0)}|${cols(1)}|${cols(2)}|${cols(3)} |${cols(4)}"); 
 val i = cols(0).toFloat 
 array += i; 
} 

//get some minimal statistics 
val count = array.length; 
var min:Double = 9999.0; 
var max:Double = 0.0; 
var total:Double = 0.0; 
for ( x <- array ) { 
 if (x < min) { min = x; } 
 if (x > max) { max = x; } 
 total += x; 
} 
val mean:Double = total / count; 

使用 Scala 格式,您可以直接使用 Scala 解释器运行脚本。在 macOS 下,有 scala 命令。Windows 系统也有类似的工具。

此外,对于其他脚本语言,您应该能够在适当的解释器中运行脚本。

如果我们从命令行窗口运行此 Scala 文件,我们可以看到预期的结果:

HTML 格式

HTML(.html)格式对应于在网页浏览器中显示页面所需的 HTML,就像在 Notebook 中看到的那样。生成的 HTML 没有任何编码逻辑;它仅包含显示类似页面所需的 HTML。

HTML 格式对传递 Notebook 的结果给另一个用户也非常有用。如果您想将 Notebook 通过电子邮件发送给其他用户(此时原始 HTML 会通过电子邮件客户端应用程序传输并可查看),那么使用 HTML 格式非常合适。

如果您有可用的 web 服务,可以插入新页面,那么 HTML 格式也很有用。如果 web 服务器不支持 Jupyter 文件(请参考本章的第一节),那么 HTML 可能是您的唯一选择。同样,即使 web 服务器支持 Jupyter,您可能也不希望直接交给用户您的源 Jupyter Notebook(.ipynb)文件。

导出的 HTML 格式在浏览器中的显示效果如下:

请注意,Jupyter 的标题信息不会显示或提供。否则,它看起来与 Jupyter 显示完全相同。

Markdown 格式

markdown(.md)格式是 超文本标记语言HTML)的简化版本。.md 文件可由某些工具使用,通常作为软件分发的 README 文件格式(在这种情况下,客户端的显示能力可能非常有限)。

例如,同一 Notebook 的 markdown 格式如下所示:


```scala211

import scala.io.Source;

//从本地复制文件 https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data

val filename = "iris.data"

//println("花萼长度, 花萼宽度, 花瓣长度, 花瓣宽度, 分类");

//将 iris 数据加载到数组中

val array = scala.collection.mutable.ArrayBuffer.empty[Float]

for (line <- Source.fromFile(filename).getLines) {

var cols = line.split(",").map(_.trim);

//println(s"${cols(0)}|${cols(1)}|${cols(2)}|${cols(3)} |${cols(4)}");

val i = cols(0).toFloat

array += i;

}

//获取一些最小统计数据

val count = array.length;

var min:Double = 9999.0;

var max:Double = 0.0;

var total:Double = 0.0;

for ( x <- array ) {

if (x < min) { min = x; }

if (x > max) { max = x; }

total += x;

}

val mean:Double = total / count;

```py 

显然,Markdown 格式是一种非常基础的显示格式。它只有少量的文本标记,帮助读者区分不同的格式。我使用 Atom 编辑器查看在解析时的显示效果:

再次显示的是非常干净的界面,仍然类似于 Jupyter Notebook 的显示,文件底部有一些奇怪的编码和大量的转义字符。不确定为什么会这样。

重结构化文本格式

重结构化文本格式(.rst)是一种简单的纯文本标记语言,偶尔用于编程文档。它看起来与前面讨论的 .md 格式非常相似。

例如,示例页面的 RST 格式如下所示:


.. code:: scala 

 import scala.io.Source; 

 //copied file locally https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data 
 val filename = "iris.data" 
 //println("SepalLength, SepalWidth, PetalLength, PetalWidth, Class"); 

 //load iris data into an array 
 val array = scala.collection.mutable.ArrayBuffer.empty[Float] 
 for (line <- Source.fromFile(filename).getLines) { 
 var cols = line.split(",").map(_.trim); 
 //println(s"${cols(0)}|${cols(1)}|${cols(2)}|${cols(3)} |${cols(4)}"); 
 val i = cols(0).toFloat 
 array += i; 
 } 

 //get some minimal statistics 
 val count = array.length; 
 var min:Double = 9999.0; 
 var max:Double = 0.0; 
 var total:Double = 0.0; 
 for ( x <- array ) { 
 if (x < min) { min = x; } 
 if (x > max) { max = x; } 
 total += x; 
 } 
 val mean:Double = total / count; 

.. parsed-literal:: 
... 

如你所见,这与前面的 Markdown 示例类似;代码大致分为几个块。

使用 Atom 显示 .rst 文件会得到如下结果:

.rst 显示没有其他格式那样漂亮。

LaTeX 格式

LaTeX 是一种来自 1970 年代末的排版格式,至今仍在许多 Unix 衍生系统中用于生成手册等。

Jupyter 使用 LaTeX 包将笔记本的图像导出为 PDF 文件。为了使其正常工作,你需要在计算机上安装这个包。在 macOS 上,这涉及以下步骤:

  • 安装 LaTeX —— Windows 和 macOS 有不同的安装方式。

  • 在 macOS 上,我尝试使用 MacTeX。你必须使用 Safari 下载该包。它提示格式错误,我不得不多次重试。

  • 在 Windows 上,我尝试使用 TeXLive。它试图下载数百个字体。

  • 以下是(macOS)字体相关的命令:

    • sudo tlmgr install adjustbox

    • sudo tlmgr install collection-fontsrecommended

请注意,这个安装过程相当繁琐。我已经安装了完整的 LaTeX,但后来又有提示要安装一个小版本的 LaTeX,并且安装字体时非常麻烦。我对这些步骤在 Windows 机器上是否能正确执行信心不足。

如果你没有完整的包,尝试下载 PDF 文件时,Notebook 会打开一个新屏幕,并显示一条长长的错误信息,告诉你缺少哪个部分。

你可以下载一个适用于你使用的操作系统的文本文件阅读器。我为我的 macOS 下载了 MacTeX。

注意:你需要一个 Tex 解释器,才能进行下一步的 PDF 下载,因为它使用 Tex 作为开发 PDF 文件的基础。

PDF 格式

PDF(.pdf)格式是一种广泛使用的展示格式,适用于多种用途。PDF 是一个很好的格式,能够向其他用户传递不可修改的内容。其他用户将无法以任何方式修改结果,但他们能够查看并理解你的逻辑。

PDF 生成依赖于 LaTeX 正确安装。这次我没能成功运行它。在之前的版本中,我曾在 Windows 和 Mac 上成功安装过。

总结

在这一章中,我们在 Notebook 服务器上共享了 Notebooks。我们将 Notebook 添加到我们的 Web 服务器,并通过 GitHub 分发了一个 Notebook。我们还探讨了将 Notebooks 转换为不同格式的方式,如 HTML 和 PDF。

在下一章,我们将探讨如何允许多个用户同时与 Notebook 进行交互。

第十一章:多用户 Jupyter Notebooks

Jupyter Notebooks 本身具有可被用户修改的能力,当用户输入数据或做出选择时,Notebook 会做出相应变化。然而,标准的 Notebook 服务器软件存在一个问题,它没有考虑到同时有多个用户在同一个 Notebook 上工作。Notebook 服务器软件是底层的 Jupyter 软件,负责显示页面并与用户交互——它根据你的 Notebook 中的指令进行显示和交互。

一个 Notebook 服务器,实际上是一个专用的互联网 Web 服务器,通常会为每个用户创建一个新的执行路径或线程,以便支持多个用户。当一个较低级别的子例程用于所有实例时,如果没有正确考虑每个用户拥有自己一组数据的情况,就会出现问题。

请注意,本章中的一些代码/安装可能在 Windows 环境下无法运行。

在本章中,我们将探讨以下内容:

  • 给出一个例子,说明在标准 Jupyter 安装中,当多个用户同时访问同一个 Notebook 时会发生的问题。

  • 使用新版本的 Jupyter,JupyterHub,它是通过扩展 Jupyter 特别为解决多用户问题而开发的。

  • 另外,使用 Docker,一种允许多个软件实例同时运行的工具,以解决这个问题。

一个示例交互式 Notebook

在本章中,我们将使用一个简单的 Notebook,要求用户提供一些信息,并显示特定的信息。

例如,我们可以有一个像这样的脚本(取自前面章节 第九章,交互式小部件):

from ipywidgets import interact 
def myfunction(x): 
    return x 
interact(myfunction, x= "Hello World "); 

这个脚本向用户展示一个文本框,文本框内的原始值为 Hello World 字符串。当用户与输入字段互动并修改其值时,脚本中的 x 变量的值也会相应变化,并显示在屏幕上。例如,我将值更改为字母 A

我们可以看到多用户问题:如果我们在另一个浏览器窗口中打开相同的页面(复制 URL,打开新浏览器窗口,粘贴 URL,按下Enter键),我们会看到完全相同的显示。新窗口应该从一个新的脚本开始,只是提示你默认的 Hello World 信息。然而,由于 Jupyter 服务器软件只期望一个用户,因此只有一个 x 变量的副本,所以它显示了它的值。

JupyterHub

一旦 Jupyter Notebooks 被共享,显然需要解决多用户问题。于是开发了一个新的 Jupyter 软件版本,叫做JupyterHub。JupyterHub 专门设计用来处理多个用户,每个用户都有自己的一组变量来操作。实际上,系统会为每个用户提供一个全新的 Jupyter 软件实例——这是一种强力方法,但它有效。

当 JupyterHub 启动时,它会启动一个中心或控制代理。该中心将启动一个用于处理 Jupyter 请求的监听器或代理。当代理接收到 Jupyter 的请求时,它将它们交给中心处理。如果中心判断这是一个新用户,它将为他们生成一个新的 Jupyter 服务器实例,并将所有后续的用户与 Jupyter 的交互附加到他们自己的服务器版本上。

安装

JupyterHub 需要 Python 3.3 或更高版本,并且我们将使用 Python 工具 pip3 来安装 JupyterHub。您可以通过在命令行中输入 Python 来检查您正在运行的 Python 版本,前言将回显当前版本。例如,请参阅以下内容:

Python 
Python 3.6.0a4 (v3.6.0a4:017cf260936b, Aug 15 2016, 13:38:16)  
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin 
Type "help", "copyright", "credits" or "license" for more information. 

如果您需要升级到新版本,请参考 python.org 上的说明,因为它们针对操作系统和 Python 版本有具体的要求。

JupyterHub 的安装方式与其他软件类似,使用以下命令:

npm install -g configurable-http-proxy
pip3 install jupyterhub

首先,安装代理。在代理安装中 -g 表示将该软件对所有用户可用:

npm install -g configurable-http-proxy 
/usr/local/bin/configurable-http-proxy -> /usr/local/lib/node_modules/configurable-http-proxy/bin/configurable-http-proxy 
/usr/local/lib 
└─┬ configurable-http-proxy@1.3.0  
 ├─┬ commander@2.9.0 
 │ └── graceful-readlink@1.0.1 
 ├─┬ http-proxy@1.13.3 
 │ ├── eventemitter3@1.2.0 
 │ └── requires-port@1.0.0 
 ├─┬ lynx@0.2.0 
 │ ├── mersenne@0.0.3 
 │ └── statsd-parser@0.0.4 
 ├── strftime@0.9.2 
 └─┬ winston@2.2.0 
 ├── async@1.0.0 
 ├── colors@1.0.3 
 ├── cycle@1.0.3 
 ├── eyes@0.1.8 
 ├── isstream@0.1.2 
 ├── pkginfo@0.3.1 
 └── stack-trace@0.0.9 

然后,我们安装 JupyterHub:

pip3.6 install jupyterhub 
Collecting jupyterhub 
 Downloading jupyterhub-0.6.1-py3-none-any.whl (1.3MB) 
 100% |████████████████████████████████| 1.4MB 789kB/s 
Collecting requests (from jupyterhub) 
 Downloading requests-2.11.1-py2.py3-none-any.whl (514kB) 
 100% |████████████████████████████████| 522kB 1.5MB/s 
Collecting traitlets>=4.1 (from jupyterhub) 
 Downloading traitlets-4.2.2-py2.py3-none-any.whl (68kB) 
 100% |████████████████████████████████| 71kB 4.3MB/s 
Collecting sqlalchemy>=1.0 (from jupyterhub) 
 Downloading SQLAlchemy-1.0.14.tar.gz (4.8MB) 
 100% |████████████████████████████████| 4.8MB 267kB/s 
Collecting jinja2 (from jupyterhub) 
 Downloading Jinja2-2.8-py2.py3-none-any.whl (263kB) 
 100% |████████████████████████████████| 266kB 838kB/s 
... 

操作

现在我们可以直接从命令行启动 JupyterHub:

jupyterhub 

这将导致在命令控制台窗口中显示以下内容:

[I 2016-08-28 14:30:57.895 JupyterHub app:643] Writing cookie_secret to /Users/dtoomey/jupyterhub_cookie_secret 
[W 2016-08-28 14:30:57.953 JupyterHub app:304]  
 Generating CONFIGPROXY_AUTH_TOKEN. Restarting the Hub will require restarting the proxy. 
 Set CONFIGPROXY_AUTH_TOKEN env or JupyterHub.proxy_auth_token config to avoid this message. 

[W 2016-08-28 14:30:57.962 JupyterHub app:757] No admin users, admin interface will be unavailable. 
[W 2016-08-28 14:30:57.962 JupyterHub app:758] Add any administrative users to `c.Authenticator.admin_users` in config. 
[I 2016-08-28 14:30:57.962 JupyterHub app:785] Not using whitelist. Any authenticated user will be allowed. 
[I 2016-08-28 14:30:57.992 JupyterHub app:1231] Hub API listening on http://127.0.0.1:8081/hub/ 
[E 2016-08-28 14:30:57.998 JupyterHub app:963] Refusing to run JuptyterHub without SSL. If you are terminating SSL in another layer, pass --no-ssl to tell JupyterHub to allow the proxy to listen on HTTP. 

请注意,脚本已完成,但默认情况下不会为您在浏览器中打开窗口,就像标准 Jupyter 安装中那样。

更重要的是最后一行的输出(也以红色打印在屏幕上),拒绝在没有 SSL 的情况下运行 JupyterHub。JupyterHub 特别构建用于多用户登录和使用单个 Notebook,因此它在抱怨期望它具有运行 SSL(以确保用户交互安全)。

最后一行的后半部分提示我们应该怎么做 —— 我们需要告诉 JupyterHub 我们没有使用证书/SSL。我们可以使用 --no-ssl 参数来做到这一点,如下所示:

Jupyterhub --no-ssl 

这将导致在控制台中显示预期的结果,并使服务器继续运行:

[I 2016-08-28 14:43:15.423 JupyterHub app:622] Loading cookie_secret from /Users/dtoomey/jupyterhub_cookie_secret 
[W 2016-08-28 14:43:15.447 JupyterHub app:304]  
 Generating CONFIGPROXY_AUTH_TOKEN. Restarting the Hub will require restarting the proxy. 
 Set CONFIGPROXY_AUTH_TOKEN env or JupyterHub.proxy_auth_token config to avoid this message. 

[W 2016-08-28 14:43:15.450 JupyterHub app:757] No admin users, admin interface will be unavailable. 
[W 2016-08-28 14:43:15.450 JupyterHub app:758] Add any administrative users to `c.Authenticator.admin_users` in config. 
[I 2016-08-28 14:43:15.451 JupyterHub app:785] Not using whitelist. Any authenticated user will be allowed. 
[I 2016-08-28 14:43:15.462 JupyterHub app:1231] Hub API listening on http://127.0.0.1:8081/hub/ 
[W 2016-08-28 14:43:15.468 JupyterHub app:959] Running JupyterHub without SSL. There better be SSL termination happening somewhere else... 
[I 2016-08-28 14:43:15.468 JupyterHub app:968] Starting proxy @ http://*:8000/ 
14:43:15.867 - info: [ConfigProxy] Proxying http://*:8000 to http://127.0.0.1:8081 
14:43:15.871 - info: [ConfigProxy] Proxy API at http://127.0.0.1:8001/api/routes 
[I 2016-08-28 14:43:15.900 JupyterHub app:1254] JupyterHub is now running at http://127.0.0.1:8000/ 

如果现在我们访问最后一行输出中显示的 URL(http://127.0.0.1:8000/)),我们将进入 JupyterHub 的登录界面:

因此,我们已经避免了需要 SSL,但我们仍然需要为系统配置用户。

JupyterHub 软件使用配置文件来确定其工作方式。您可以使用 JupyterHub 生成配置文件,并使用以下命令提供默认值:

jupyterhub --generate-config 
Writing default config to: jupyterhub_config.py 

生成的配置文件可用近 500 行。示例文件的开头如下所示:

# Configuration file for jupyterhub.
c = get_config()

#------------------------------------------------------------------------------
# JupyterHub configuration
#------------------------------------------------------------------------------

# An Application for starting a Multi-User Jupyter Notebook server.
# JupyterHub will inherit config from: Application

# Include any kwargs to pass to the database connection. See
# sqlalchemy.create_engine for details.
# c.JupyterHub.db_kwargs = {}

# The base URL of the entire application

# c.JupyterHub.base_url = '/'
…

如您所见,大多数配置设置前面都有一个井号(#),表示它们已被注释掉。提到的设置是将要应用的默认值。如果您需要更改某个设置,您需要去掉前面的井号并更改等号(=)右侧的值。顺便说一下,这是一种测试更改的好方法:进行一次更改,保存文件,尝试更改,继续进行其他更改。如果过程中某个更改未按预期工作,您只需恢复井号前缀即可恢复到工作状态。

我们将查看一些可用的配置选项。值得注意的是,文件中的许多设置是 Python 设置,而不是特定于 JupyterHub 的。项目列表包括此处显示的项目:

区域 描述
JupyterHub JupyterHub 本身的设置
LoggingConfigurable 日志信息布局
SingletonConfigurable 仅允许一个实例的可配置项
Application 日期格式和日志级别
Security SSL 证书
Spawner Hub 如何为新用户启动新的 Jupyter 实例
LocalProcessSpawner 使用 popen 作为用户启动本地进程
Authenticator 主要 API 是一个方法 authenticate
PAMAuthenticator 与 Linux 交互以登录
LocalAuthenticator 检查本地用户,并且如果用户存在,可以尝试创建它们

正在继续操作

我没有更改配置文件就使我的安装正常运行。默认情况下,配置使用 PEM 系统,它会连接到您正在运行的操作系统,以传入凭据(就像登录到机器一样)进行验证。

如果在尝试登录到您的 JupyterHub 安装时,控制台日志中出现了 JupyterHub single-user server requires notebook >= 4.0 消息,您需要使用以下命令更新基础 Jupyter:

pip3 install jupyter

这将把您的基础 Jupyter 更新到最新版本,目前是 4.1。

如果您没有安装 pip3,您需要升级到 Python 3 或更高版本。有关您的系统需要采取的步骤,请参阅 python.org 上的文档。

现在,您可以使用以下命令行启动 JupyterHub:

jupyterhub --no-ssl

使用您登录机器时使用的相同凭据登录到登录页面(如前所示)(记住 JupyterHub 使用 PEM,它会调用您的操作系统来验证凭据)。您将进入一个非常像标准 Jupyter 首页的页面:

它看起来非常相似,只是现在屏幕右上角有两个额外的按钮:

  • 控制面板

  • 注销

单击注销按钮将您从 JupyterHub 注销并重定向到登录页面。

点击控制面板按钮会带你进入一个新屏幕,提供两个选项,如下所示:

  • 停止我的服务器

  • 我的服务器

点击“停止我的服务器”按钮会停止你的 Jupyter 安装,并带你进入一个只包含一个按钮的页面:我的服务器(如下节所示)。你可能还注意到命令行控制台日志中发生的变化:

[I 2016-08-28 20:22:16.578 JupyterHub log:100] 200 GET /hub/api/authorizations/cookie/jupyter-hub-token-dtoomey/[secret] (dtoomey@127.0.0.1) 13.31ms 
[I 2016-08-28 20:23:01.181 JupyterHub orm:178] Removing user dtoomey from proxy 
[I 2016-08-28 20:23:01.186 dtoomey notebookapp:1083] Shutting down kernels 
[I 2016-08-28 20:23:01.417 JupyterHub base:367] User dtoomey server took 0.236 seconds to stop 
[I 2016-08-28 20:23:01.422 JupyterHub log:100] 204 DELETE /hub/api/users/dtoomey/server (dtoomey@127.0.0.1) 243.06ms 

点击“我的服务器”按钮将带你回到 Jupyter 首页。如果你之前点击过“停止我的服务器”按钮,那么底层的 Jupyter 软件将会重新启动,正如你在控制台输出中可能会注意到的那样(如下所示):

I 2016-08-28 20:26:16.356 JupyterHub base:306] User dtoomey server took 1.007 seconds to start 
[I 2016-08-28 20:26:16.356 JupyterHub orm:159] Adding user dtoomey to proxy /user/dtoomey => http://127.0.0.1:50972 
[I 2016-08-28 20:26:16.372 dtoomey log:47] 302 GET /user/dtoomey (127.0.0.1) 0.73ms 
[I 2016-08-28 20:26:16.376 JupyterHub log:100] 302 GET /hub/user/dtoomey (dtoomey@127.0.0.1) 1019.24ms 
[I 2016-08-28 20:26:16.413 JupyterHub log:100] 200 GET /hub/api/authorizations/cookie/jupyter-hub-token-dtoomey/[secret] (dtoomey@127.0.0.1) 10.75ms 

JupyterHub 概述

总结一下,通过 JupyterHub,我们安装了一个 Jupyter 实例,它会为每个用户保持一个独立的 Jupyter 软件实例,从而避免变量值的冲突。软件能够识别是否需要实例化新的 Jupyter 实例,因为用户会登录应用程序,而系统会维护用户列表。

Docker

Docker 是另一种可以让多个用户共享同一个 Notebook 而不发生冲突的机制。Docker 是一个允许你将应用程序集成成镜像并在容器中运行的系统。Docker 适用于大多数环境。Docker 允许在同一台机器上运行多个镜像实例,但保持独立的地址空间。因此,Docker 镜像的每个用户都有自己的软件实例和独立的数据/变量集。

每个镜像是运行所需软件的完整堆栈,例如,Web 服务器、Web 应用程序、API 等。

将 Notebook 的镜像进行思考并不是一件难事。该镜像包含 Jupyter 服务器代码和你的 Notebook,结果是一个完全独立的单元,不与其他实例共享任何空间。

安装

安装 Docker 涉及下载最新的文件(macOS 的 docker.dmg 文件和 Windows 的 .exe 安装文件),然后将 Docker 应用程序复制到你的 Applications 文件夹中。Docker QuickStart 终端 是大多数开发者使用的主要应用程序。Docker QuickStart 会在本地机器上启动 Docker,为 Docker 应用程序分配一个 IP 地址 / 端口号,并进入 Docker 终端。一旦 QuickStart 完成,如果你已安装镜像,你可以访问你的应用程序(在本例中是 Jupyter Notebook)。

从 Docker 终端,你可以加载镜像、删除镜像、检查状态等。

启动 Docker

如果你运行 Docker QuickStart,你将进入 Docker 终端窗口,显示类似于以下内容:

bash --login '/Applications/Docker/Docker Quickstart Terminal.app/Contents/Resources/Scripts/start.sh' 
Last login: Tue Aug 30 08:25:11 on ttys000 
bos-mpdc7:Applications dtoomey$ bash --login '/Applications/Docker/Docker Quickstart Terminal.app/Contents/Resources/Scripts/start.sh' 

Starting "default"... 
(default) Check network to re-create if needed... 
(default) Waiting for an IP... 
Machine "default" was started. 
Waiting for SSH to be available... 
Detecting the provisioner... 
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command. 
Regenerate TLS machine certs?  Warning: this is irreversible. (y/n): Regenerating TLS certificates 
Waiting for SSH to be available... 
Detecting the provisioner... 
Copying certs to the local machine directory... 
Copying certs to the remote machine... 
Setting Docker configuration on the remote daemon... 

 ##         . 
 ## ## ##        == 
 ## ## ## ## ##    === 
 /"""""""""""""""""\___/ === 
 ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~ 
 \______ o           __/ 
 \    \         __/ 
 \____\_______/ 

docker is configured to use the default machine with IP 192.168.99.100 
For help getting started, check out the docs at [`docs.docker.com`](https://docs.docker.com) 

(显示末尾附近的奇怪图形是鲸鱼的字符表示——Docker 的标志。)

你可以从输出中看到以下内容:

  • Docker 机器已启动——Docker 机器控制着在你的空间中运行的镜像。

  • 如果你正在使用证书,证书将被复制到你的 Docker 空间中。

  • 最后,它会告诉你在访问 Docker 实例时使用的 IP 地址——应该是你正在使用的机器的 IP 地址。

为 Docker 构建你的 Jupyter 镜像

Docker 知道包含运行应用程序所需整个软件堆栈的镜像。我们需要构建一个包含 Notebook 的镜像,并将其放入 Docker 中。

我们需要下载所有必要的 Jupyter-Docker 编码。在 Docker 终端窗口中,我们运行以下命令:

$ docker pull jupyter/all-spark-notebook 
Using default tag: latest 
latest: Pulling from jupyter/all-spark-notebook 
8b87079b7a06: Pulling fs layer  
872e508604af: Pulling fs layer  
8e8d83eda71c: Pull complete  
... 

这是一个较大的下载过程,需要一些时间。它正在下载并安装运行 Jupyter 所需的所有可能组件。请记住,每个镜像都是完全自包含的,所以每个镜像都有运行 Jupyter 所需的全部软件。

下载完成后,我们可以使用以下命令启动一个 Notebook 镜像:

docker run -d -p 8888:8888 -v /disk-directory:/virtual-notebook jupyter/all-spark-notebook 
The parts of this command are: 
  • docker run:告诉 Docker 执行一个镜像的命令。

  • -d:将镜像作为服务器(守护进程)运行,直到用户手动停止。

  • -p 8888:8888:将内部端口 8888 暴露给外部用户,使用相同的端口地址。Notebook 默认已经使用端口 8888,所以我们只是表示暴露相同的端口。

  • -v /disk-directory:/virtual-notebook:将来自 disk-directory 的 Notebook 以 virtual-notebook 名称暴露出来。

  • 最后的参数是使用 all-spark-notebook 作为该镜像的基础。在我的例子中,我使用了以下命令:

$ docker run -d -p 8888:8888 -v /Users/dtoomey:/dan-notebook jupyter/all-spark-notebook 
b59eaf0cae67506e4f475a9861f61c01c5af3556489992104c4ce39343e8eb02

显示的长 hex 数字是镜像标识符。我们可以使用 docker ps -l 命令检查镜像是否正在运行,这个命令会列出我们 Docker 中的所有镜像:

$ docker ps -l 
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                    NAMES 
b59eaf0cae67        jupyter/all-spark-notebook   "tini -- start-notebo"   8 seconds ago       Up 7 seconds        0.0.0.0:8888->8888/tcp   modest_bardeen 

显示的部分如下:

  • 第一个名称 b59... 是容器的分配 ID。Docker 中的每个镜像都被分配给一个容器。

  • 镜像是 jupyter/all-spark-notebook,它包含运行 Notebook 所需的所有软件。

  • 该命令告诉 Docker 启动镜像。

  • 端口访问点是我们预期的:8888

  • 最后,Docker 为每个正在运行的镜像分配随机名称 modest_bardeen(不确定为什么这么做)。

如果我们此时尝试访问 Docker Jupyter,我们将被要求为系统设置安全性,如此显示所示:

一旦我们设置好安全性,我们应该能够通过浏览器访问 Notebook,地址为 http:// 127.0.0.1:8888。我们在 Docker 启动时看到过前面的 IP 地址(0.0.0.0),并且我们使用的是指定的端口 8888

你可以在左上角看到 URL。在其下方,我们有一个标准的空 Notebook。所使用的 Docker 镜像包含了所有最新版本,因此你无需做任何额外操作就能获取更新的软件或组件。你可以通过下拉 "New" 菜单来查看可用的语言选项:

Docker 总结

我们已经安装了 Docker,并且创建了一个包含我们的 Notebook 的镜像。我们还将该 Docker 镜像放入 Docker 中,并且访问了我们的 Docker Notebook 镜像。

总结

在本章中,我们学习了如何暴露一个 Notebook,以便多个用户可以同时使用该 Notebook。我们看到了一个错误发生的例子。我们安装了一个解决该问题的 Jupyter 服务器,并且使用 Docker 来缓解这个问题。

在下一章,我们将会看看 Jupyter 的一些即将发布的功能增强。

第十二章:接下来是什么?

在这一章节中,我们将做以下内容:

  • 强调一些即将到来的 Jupyter 特性,您可能会期待它们。(这些例子大多数是计划的截图和描述,没有提供任何代码。这些构思仍处于初期阶段,且尚未广泛提供。)

  • 给出一个在标准 Jupyter 安装中多个用户访问同一个 Notebook 时可能出现的问题的例子。

JupyterHub

我们在前一章节中已经看到过 JupyterHub。

JupyterHub 是 Jupyter 的多用户实例。所有需要多个用户与 Jupyter 实现交互的方向都将通过 Jupyter 展示。

第一步是支持多个用户。这要求 Jupyter 为每个用户分配独立的空间。

下一步是共享服务,由一个 Jupyter 实现访问,但由多个用户使用。服务需要有身份验证(这真的是用户 xyzzy 吗?),Jupyter 正在朝着使用 OAUTH 标准的方向发展,OAUTH 是 Web 服务身份验证的标准。

JupyterLab

我们在前一章节中已经讨论过 JupyterLab。

JupyterLab 致力于为 Jupyter 制作下一代用户界面设计。

扩展性

目前 Jupyter 的实现存在扩展性问题,因为随着用户数量的增加,性能会显著下降。

随着 Jupyter 的广泛应用,随着使用者的增加,问题变得更加显著。

Jupyter 团队正在将他们的大部分近期努力集中在提升 Jupyter 的可扩展性,以便无缝处理大量用户。正在开发的一些解决方案包括以下内容:

  • 代理 API 在浏览器和服务器服务之间提供 API,从而扩展应用程序

  • MOAR 服务器,允许每个用户使用多个服务器

  • 在整个服务器/服务中进一步整合 OAUTH

  • Jupyter 团队在市场发布前进行的压力测试工作

自定义前端

随着用户界面组件处理的提取,用户现在可以为他们的实现定制完全独立的前端。所有入口点都有文档记录,用户可以按需执行必要的步骤。

交互式计算标准

Jupyter 本质上是一个交互式计算平台。Jupyter 团队正在与行业成员合作,在以下领域制定标准(Jupyter 将符合这些标准):

  • Notebook 文件格式

  • 交互式计算协议

  • 内核交互

对于本章节,我们将使用一个简单的 Notebook,它会要求用户提供一些信息,示例如下截图:

总结

在这一章节中,我们学习了如何暴露 Notebook,以便多个用户可以同时使用一个 Notebook。我们看到出现错误的例子,安装了一个解决此问题的 Jupyter 服务器,并且使用 Docker 来缓解该问题。

posted @ 2025-07-20 11:31  绝不原创的飞龙  阅读(7)  评论(0)    收藏  举报