使用Jupyter Notebooks(使用Jupytext和Papermill)自动生成报告
本文源码下载:github
Jupyter笔记本是交互式运行代码以及编写带有数据和图表的叙述的最佳可用工具之一。鲜为人知的是它们可以方便地进行版本控制并自动运行。
您是否有Jupyter笔记本,其中包含您定期手动运行的绘图和图形?使用同一笔记本而不是使用脚本启动自动报告系统会不会很好?如果此脚本甚至可以将一些参数传递给它运行的笔记本怎么办?
这篇文章分几步说明了如何具体地做到这一点, 包括在生产环境中。
示例笔记本
我们将向您展示如何进行版本控制,自动运行和发布依赖于参数的笔记本。例如,我们将使用一个笔记本来描述特定年份的世界人口和国内生产总值。使用简单:只需year在第一个单元格中更改变量,然后重新运行,即可获得所选年份的图表。但这需要手动干预。如果可以自动执行更新并为每个可能的year参数值生成报告,将会更加方便(更一般而言,笔记本计算机不仅可以基于一些用户提供的参数,而且还可以通过与参数的连接来更新其结果。数据库等)。
版本控制
在专业环境中,笔记本电脑是由数据科学家设计的,但是在生产环境中运行笔记本电脑的任务可能会由其他团队来处理。因此,一般而言,人们必须共享笔记本。最好通过版本控制系统来完成。
Jupyter笔记本因其版本控制的难度而闻名。让我们考虑上面的笔记本,文件大小为3 MB,其中大部分由嵌入式Plotly库贡献。如果我们删除第二个代码单元的输出,笔记本将小于80 KB。删除所有输出后,大小仅为1.75 KB。这表明它的多少内容与纯代码无关!如果我们不注意的话,笔记本中的代码更改将丢失大量的二进制内容。
为了获得有意义的差异,我们使用Jupytext(免责声明:我是Jupytext的作者)。Jupytext可以使用pip或安装conda。重新启动笔记本服务器后,Jupyter中将出现一个Jupytext菜单:
我们单击将笔记本与Markdown配对,保存笔记本...,然后获得笔记本的两种表示形式:(world_fact.ipynb 具有输入和输出单元格)和 world_fact.md(仅具有输入单元格)。
Jupytext将笔记本表示为Markdown文件与所有主要的Markdown编辑器和查看器兼容,包括GitHub和VS Code。Markdown版本例如由GitHub呈现为:
如您所见,Markdown文件不包含任何输出。实际上,由于我们只需要共享笔记本代码,因此我们不希望在此阶段使用它。Markdown文件还具有非常清晰的差异历史记录,这使笔记本的版本控制变得简单。
该world_facts.md文件由Jupyter当您保存在笔记本自动更新。反之亦然!如果world_facts.md使用文本编辑器进行修改,或者通过从版本控制系统中提取最新的内容进行修改,则在浏览器中刷新笔记本时,更改将显示在Jupyter中。
在我们的版本控制系统中,我们只需要跟踪Markdown文件(甚至可以显式忽略所有.ipynb文件)。显然,执行笔记本的团队需要重新生成world_fact.ipynb文档。为此,他们在命令行中使用Jupytext:
$ jupytext world_facts.md --to ipynb
[jupytext] Reading world_facts.md
[jupytext] Writing world_facts.ipynb
现在,我们正在正确地对笔记本进行版本控制。差异历史更加清晰。例如,查看我们的报告中增加的国内生产总值的样子:
Jupyter笔记本作为脚本?
作为Markdown表示的替代方法,我们可以 world_facts.py 使用Jupytext将笔记本与脚本配对。如果您的笔记本中包含的代码多于文本,则应尝试一下。这通常是迈向完整而高效的长笔记本重构的第一步,一旦笔记本被表示为脚本,您就可以提取任何复杂的代码,并使用IDE中的重构工具将其移至(经过单元测试的)库中。
JupyterLab,JupyterHub,Binder,Nteract,Colab和Cloud笔记本?
您是否使用JupyterLab而不是Jupyter Notebook?不用担心:以上方法在这种情况下也适用。您只需要为JupyterLab使用Jupytext扩展名,而不是使用Jupytext菜单。如果您想知道,Jupytext也可以在JupyterHub和Binder中使用。
如果您使用其他笔记本编辑器,例如Nteract桌面,CoCalc,Google Colab或其他云笔记本编辑器,则可能无法使用Jupytext作为编辑器中的插件。在这种情况下,您只需在命令行中使用Jupytext。闭上你的笔记本电脑和注入的配对信息到world_facts.ipynb 与
$ jupytext --set-formats ipynb,md world_facts.ipynb
然后保持两个表示与
$ jupytext --sync world_facts.ipynb
笔记本参数
Papermill是用于执行带参数笔记本的参考库。
造纸厂需要知道哪个单元格包含笔记本参数。只需parameter使用Jupyter Notebook中的单元格工具栏在该单元格中添加标签即可完成此操作:
在JupyterLab中,您可以使用celltags扩展名。
并且,如果您愿意,也可以直接world_facts.md在此处编辑并添加标签:
year = 2000
自动执行
现在,我们拥有在生产服务器上执行笔记本所需的所有信息。
生产环境
为了执行笔记本,我们需要知道它应该在哪个环境中运行。在此示例中,当我们使用Python笔记本工作时,我们将其依赖关系列在requirements.txt文件中,这是Python项目的标准。
为简单起见,我们还将笔记本工具包含在同一环境中,即添加jupytext和添加papermill到同一requirements.txt文件中。严格来说,这些工具可以在另一个Python环境中安装和执行。
使用以下任一方法创建相应的Python环境
$ conda create -n run_notebook --file requirements.txt -y
或者
$ pip install -r requirements.txt
(如果在虚拟环境中)。
请注意,该requirements.txt文件只是指定执行环境的一种方式。该重复性执行环境规范的粘结剂队伍为主题的最完整的参考资料之一。
持续集成
优良作法是测试对笔记本计算机或其要求的每个新贡献。为此,您可以使用例如Travis CI(连续集成解决方案)。您仅需要以下两个命令:
pip install -r requirements.txt
安装依赖项jupytext world_facts.md --set-kernel - --execute
在当前的Python环境中测试笔记本的执行情况。
您可以在我们的.travis.yml
文件中找到一个具体示例。
我们已经在自动执行笔记本了,不是吗?Travis会告诉我们是否在项目中引入了回归…进展如何!但是我们还没有100%完成,因为我们承诺要使用参数执行笔记本。
使用正确的内核
Jupyter笔记本与内核(即,指向本地Python环境的指针)相关联,但是该内核可能在您的生产计算机上不可用。在这种情况下,我们只需更新笔记本内核,以指向我们刚刚创建的环境:
$ jupytext world_facts.ipynb --set-kernel-
请注意,--set-kernel -
上面的减号表示当前的Python环境。在我们的示例中,得出:
[jupytext] Reading world_facts.ipynb
[jupytext] Updating notebook metadata with '{"kernelspec": {"name": "python3", "language": "python", "display_name": "Python 3"}}' [jupytext] Writing world_facts.ipynb (destination file replaced)
如果您要使用另一个内核,只需将内核名称传递给该--set-kernel选项(您可以使用来获取所有可用内核的列表,jupyter kernelspec list
和/或使用来声明一个新内核python -m ipykernel install --name kernel_name --user
)。
用参数执行笔记本
现在,我们可以使用Papermill执行笔记本了。
$ papermill world_facts.ipynb world_facts_2017.ipynb -p year 2017
Input Notebook: world_facts.ipynb Output Notebook: world_facts_2017.ipynb 100%|██████████████████████████████████████████████████████| 8/8 [00:04<00:00, 1.41it/s]
大功告成!笔记本已执行,文件world_facts_2017.ipynb
包含输出。
发布笔记本
现在该交付刚刚执行过的笔记本了。也许您想在邮箱中找到它?或者,也许您希望获得一个可以查看结果的URL?我们介绍了几种方法。
GitHub可以显示Jupyter笔记本。这是一个方便的解决方案,因为您可以轻松选择谁可以访问存储库。只要您在笔记本中不包含任何交互式JavaScript图或小部件,此方法就可以很好地工作(GitHub忽略JavaScript部分)。就我们的笔记本而言,交互式绘图未出现在GitHub上,因此我们需要另一种方法。
另一个选择是使用Jupyter Notebook Viewer。该nbviewer服务可以使任何笔记本电脑是在GitHub上公开。因此,我们的笔记本在此处正确渲染。如果您的笔记本不是公开的,则可以选择在本地安装nbviewer。
或者,您可以将执行的笔记本转换为HTML,并将其发布在GitHub页面上,或在您自己的HTML服务器上,或通过电子邮件发送。轻松将笔记本转换为HTML
$ jupyter nbconvert world_facts_2017.ipynb --to html
[NbConvertApp] Converting notebook world_facts_2017.ipynb to html [NbConvertApp] Writing 3361863 bytes to world_facts_2017.html
生成的HTML文件包括以下代码单元:
但是,也许您不想看到HTML中的输入单元格?您只需要添加--no-input:
$ jupyter nbconvert --to html --no-input world_facts_2017.ipynb --output world_facts_2017_report.html
您会得到一份更清晰的报告:
将独立的HTML文件作为附件发送到电子邮件中很容易。也可以将报告嵌入到电子邮件正文中(但交互式绘图无法使用)。
最后,如果您正在寻找一份完善的报告并且对LaTeX有所了解,则可以尝试Jupyter的nbconvert命令的PDF导出选项。
使用管道
使用命名文件的替代方法是使用管道。jupytext,nbconvert并且papermill所有人都支持它们。以前的命令的单行替代是:
$ cat world_facts.md \
| jupytext --from md --to ipynb --set-kernel - \
| papermill -p year 2017 \
| jupyter nbconvert --stdin --output world_facts_2017_report.html
结论
现在,您应该能够基于Jupyter笔记本电脑建立用于在生产中生成报告的完整管道。我们已经看到了如何:
- 使用Jupytext版本控制笔记本
- 在多个用户之间共享笔记本及其依赖项
- 持续集成测试笔记本
- 使用Papermill执行带有参数的笔记本
- 最后,如何发布笔记本(在GitHub或nbviewer上),或将其呈现为静态HTML页面。
本示例中使用的技术完全基于Jupyter Project,它是数据科学的事实上的标准。这里使用的工具都是开源的,并且可以与任何持续集成框架一起很好地工作。
您拥有计划和交付经过微调的,无代码的报告所需的一切!
结语
这里使用的工具是用Python编写的。但是它们与语言无关。感谢Jupyter框架,它们实际上适用于存在Jupyter内核的40多种编程语言中的任何一种。
现在,假设您已经编写了一个包含一些Bash命令行的文档,就像这篇博客文章一样。安装Jupytext和bash内核,博客文章将成为此交互式Jupyter笔记本!
更进一步,我们是否应该确保帖子中的每条指令都切实有效?我们通过持续的集成来做到这一点……扰流板警报:就像jupytext --execute README.md!一样简单!
致谢
Marc感谢Eric Lebigot和Florent Zara对本文的贡献,并感谢CFM通过其开源计划支持这项工作。
关于作者
本文由Marc Wouts撰写。Marc于2012年加入CFM的研究团队,从事过从最佳交易到投资组合构建的一系列研究项目。
Marc一直对寻找有效的工作流以进行涉及数据和代码的协作研究感兴趣。在2015年,他编写了一个内部工具,用于在Atlassian的Confluence Wiki上发布Jupyter和R Markdown笔记本,为在笔记本上进行协作提供了第一个解决方案。在2018年,他编写了Jupytext这个开源程序,该程序可简化Jupyter笔记本的版本控制。Marc也对数据可视化感兴趣,并在CFM协调有关此主题的工作组。
Marc于2007年获得巴黎狄德罗大学的概率论博士学位。
免责声明
本文档中包含的所有观点均构成其作者的判断,并不一定反映资本基金管理公司或其任何分支机构的观点。本文档中提供的信息仅是一般信息,不构成投资或其他建议,如有更改,恕不另行通知。以上成果源自Google翻译, 如果有错都是Google的锅,骂他就对了。微信 yujiabuao.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。