如何使用ipywidgets来使你的Jupyter notebook具有交互性(控件交互)

3,390 阅读10分钟

你是否曾经创建过一个基于Python的Jupyter notebook,并分析了你想以多种不同方式探索的数据?例如,你可能想看一个数据图,但要用十种不同的方式来筛选它。你有什么办法来查看这十种不同的结果?

  1. 复制并粘贴一个单元格,改变每个单元格的筛选方式,然后执行该单元格。你最终会得到十个不同的单元格,十个不同的值。
  2. 修改同一个单元格,执行它并查看结果,然后再修改它,十次。
  3. 对笔记本进行参数化(也许使用像Papermill这样的工具),用十组不同的参数执行整个笔记本。
  4. 上述的一些组合。

如果我们想要快速互动和探索数据的能力,这些都是不理想的。这些选项也很容易出现输入错误或大量的额外编辑工作。对于笔记本的原始开发者来说,它们可能很好用,但让一个不懂Python语法的用户来修改变量和重新执行单元格可能不是最好的选择。如果你能给用户一个简单的表单,加上一个按钮,他们可以修改表单并看到他们想要的结果,那会怎么样呢?

事实证明,你可以在Jupyter中很容易地做到这一点,而不需要创建一个完整的webapp。这可以通过 [ipywidgets](https://ipywidgets.readthedocs.io/en/latest/index.html)也被称为widget。在这篇文章中,我将向你展示建立几个简单的表单来查看和分析一些数据的基本知识。

什么是widget?

Jupyter部件是一些特殊的代码位,它们将在你的笔记本中嵌入JavaScript和html,并在笔记本中执行时在你的浏览器中呈现出视觉效果。这些组件允许用户与小工具进行互动。这些部件可以在某些操作上执行代码,使你可以更新单元格,而无需用户重新执行,甚至修改任何代码。

开始使用

首先,你需要确保ipywidgets 已经安装在你的环境中。这将在一定程度上取决于你使用的Jupyter环境。对于较早的Jupyter和JupyterLab的安装,请确保查看文档中的细节但对于基本的安装,只需使用pip

pip install ipywidgets

或对于conda

conda install -c conda-forge ipywidgets

在大多数情况下,这应该是你需要做的所有事情,以使事情运行。

例子

与其马上去看所有的widget并进入细节,不如让我们抓取一些有趣的数据并手动探索它。然后,我们将使用widget对这些数据探索进行更多的互动。让我们从芝加哥数据门户网站抓取一些数据--特别是他们的当前活跃商业执照的数据集。请注意,如果你只是运行下面的代码,你只能得到1000行的数据。请查看文档,了解如何获取所有的数据。

注意:所有这些代码都是在Jupyter笔记本中使用Python 3.8.6编写的。虽然这篇文章展示了输出结果,但体验小工具的最好方法是在你自己的环境中与它们互动。你可以在这里下载这篇文章的笔记本。

import pandas as pd
df = pd.read_csv('https://data.cityofchicago.org/resource/uupf-x98q.csv')
df[['LEGAL NAME', 'ZIP CODE', 'BUSINESS ACTIVITY']].head()

正如我们从数据中看到的,商业活动是相当冗长的,但邮编是一个简单的方法,可以对数据进行一些简单的搜索和过滤。对于我们较小的数据集,让我们只抓取有20家或更多企业的邮政编码。

zips = df.groupby('ZIP CODE').count()['ID'].sort_values(ascending=False)
zips = list(zips[zips > 20].index)
zips
[60618, 60622, 60639, 60609, 60614, 60608, 60619, 60607]

现在,一个合理的过滤数据的方案可能是创建一个按邮编过滤的报告,显示企业的法定名称和地址,按许可证的到期日排序。这在pandas中是一个相当简单的表达式(即使有些混乱)。例如,在这个数据集中,我们可以取最上面的邮政编码,然后像这样看几列。

df.loc[df['ZIP CODE'] == zips[0]].sort_values(by='LICENSE TERM EXPIRATION DATE', ascending=False)[['LEGAL NAME', 'ADDRESS', 'LICENSE TERM EXPIRATION DATE']]

现在,如果有人希望能够对不同的邮编运行这个报告,查看不同的列,并按其他列进行排序,那该怎么办?用户必须能够自如地编辑上面的单元格,重新运行它,并可能执行其他单元格来寻找列名和其他值。

使用小组件

相反,我们可以使用widget来制作一个表单,让这种交互在视觉上得到执行。在这篇文章中,你将了解到足够多的widget来构建一个表单并动态地显示结果。

小工具类型

因为我们大多数人都熟悉网络浏览器中的表单,所以把widget看作是典型表单的一部分是有意义的。小部件可以表示数字、布尔值或文本值。它们可以是预先存在的列表的选择器,或者可以接受自由文本(或密码文本)。你也可以用它们来显示格式化的输出或图像。小组件的完整列表更详细地描述了它们。你也可以创建你自己的自定义部件,但就我们的目的而言,我们将能够用标准部件完成所有的工作。

小组件只是一个一旦创建就可以在Jupyter笔记本中显示的对象。它将呈现自己(及其底层内容)并(可能)允许用户互动。

制作一个表单

对于我们的表格,我们将需要收集四项信息。

  1. 要过滤的邮编
  2. 要排序的列
  3. 排序是升序还是降序
  4. 要显示的列。

这四条信息将被以下表单元素所捕获。

  1. 一个选择下拉菜单
  2. 一个选择下拉框
  3. 一个复选框
  4. 一个多选列表

这三个小组件将提供一个小组件的快速介绍,一旦你知道如何实例化和使用一个小组件,其他的小组件也很相似。在我们可以创建一个部件之前,我们需要导入库。让我们先来看看下拉菜单。

import ipywidgets as widgets


widgets.Dropdown(
    options=zips,
    value=zips[0],
    description='Zip Code:',
    disabled=False,
)

当然,仅仅创建一个对象并不能让我们使用它,所以我们需要把它分配给一个变量,display 函数可以用来渲染它,和我们上面看到的一样。

zips_dropdown = widgets.Dropdown(
    options=zips,
    value=zips[0],
    description='Zip Code:',
    disabled=False,
)

display(zips_dropdown)

我们可以很容易地对列做同样的事情。

columns_dropdown = widgets.Dropdown(
    options=df.columns,
    value=df.columns[4],
    description='Sort Column:',
    disabled=False,
)

display(columns_dropdown)

而对于布尔值,你有几个选择。你可以做一个CheckBoxToggleButton 。我会选择第一个。

sort_checkbox = widgets.Checkbox(
    value=False,
    description='Ascending?',
    disabled=False)
display(sort_checkbox)

最后,对于这个例子,我们希望能够选择我们想在输出中看到的所有列。我们将使用SelectMultiple 。注意,如果你使用shift和ctrl(或Mac上的Command)键来选择多个选项。

columns_selectmultiple = widgets.SelectMultiple(
    options=df.columns,
    value=['LEGAL NAME'],
    rows=10,
    description='Visible:',
    disabled=False
)
display(columns_selectmultiple)

最后,我们将显示一个按钮,我们可以点击它来强制更新。注意,我们最终不会需要这个,有一个更简单的方法来与我们的元素互动,但按钮在很多情况下都是有用的)。

button = widgets.Button(
    description='Run',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Run report',
    icon='check' # (FontAwesome names without the `fa-` prefix)
)
display(button)

处理输出

在我们把按钮挂到一个函数上之前,我们需要确保我们能捕捉到我们函数的输出。如果我们想查看一个DataFrame ,或打印文本,或记录一些信息到stdout,我们需要能够捕获这些信息,并在必要时清除它。这就是Output widget的作用。注意,你不必使用输出部件,但如果你想让你的输出出现在某个单元格,你就需要使用这个。显示Output widget的单元格将呈现结果。

out = widgets.Output(layout={'border': '1px solid black'})

将其全部连接起来

现在我们已经生成了所有的用户界面组件,我们如何将它们全部显示在一个地方,并将它们与生成的动作挂钩呢?

首先,让我们创建一个简单的布局,将所有的项目放在一起。

box = widgets.VBox([zips_dropdown, columns_dropdown, sort_checkbox, columns_selectmultiple, button])
display(box)

处理事件

对于能够产生事件的部件,你可以提供一个接收事件的函数。对于一个Button ,事件是on_click ,它需要一个将接受一个参数的函数,即Button 本身。如果我们使用上面创建的Output (作为使用with 语句的上下文管理器),点击按钮将导致文本 "Button clicked "被附加到单元格输出中。请注意,收到输出的单元格将是渲染Output 的地方。

def on_button_clicked(b):
    with out:
        print("Button clicked.")

button.on_click(on_button_clicked, False)

一个更好的方法来连接东西

上面的例子很简单,但没有告诉我们如何从其他输入中获取数值。另一种方法是使用interact 。它既可以作为一个函数,也可以作为一个函数装饰器,自动创建小部件,让你可以交互地改变函数的输入。基于命名的参数类型,它将生成一个允许你改变该值的小部件。使用interact 是一个快速的方法来提供围绕一个函数的用户互动。每次小部件被更新时,该函数将被调用。当你移动滑块时,如果复选框被选中,数字的平方将被打印出来,否则数字将只是被打印出来,没有变化。

def my_function2(x, y):
    if y:
        print(x*x)
    else:
        print(x)

interact(my_function2,x=10,y=False);

注意,你可以提供更多的信息给interact ,以提供更合适的用户界面元素(见文档中的例子)。但既然我们已经做了小部件,我们可以直接用这些小部件来代替。最好的方法是使用另一个函数,interactiveinteractive ,就像interactive一样,但允许你与创建的widget进行交互(或直接提供它们),并在你想要的时候显示值。由于我们已经做了一些小部件,我们可以直接让interactive ,通过提供每个小部件作为关键字参数来了解它们。第一个参数是一个函数,该函数的参数需要与随后的关键字参数相匹配,以便互动。每次我们改变表单中的一个值时,该函数将被调用,其值来自表单部件。只需几行代码,我们现在就有了一个用于查看和过滤这些数据的互动工具。

但首先,我将制作一个有输出的单元格来接收显示。

report_output = widgets.Output()
display(report_output)

from ipywidgets import interactive

def filter_function(zipcode, sort_column, sort_ascending, view_columns):
    filtered = df.loc[df['ZIP CODE'] == zipcode].sort_values(by=sort_column, ascending=sort_ascending)[list(view_columns)]
    with report_output:
        report_output.clear_output()
        display(filtered)
    
interactive(filter_function, zipcode=zips_dropdown, sort_column=columns_dropdown,
                    sort_ascending=sort_checkbox, view_columns=columns_selectmultiple) 

现在,上面早先创建的同样的表格在单元格中被呈现出来。输出将出现在执行display(report_output) 行的哪个单元格。当你修改任何一个表单元素时,产生的过滤后的DataFrame ,将显示在该单元格中。

总结

这只是对使用ipywidgets ,使Jupyter笔记本更具交互性的一个快速概述。即使你对编辑Python代码和重新执行单元格以更新和探索数据感到很舒服,小组件也可能是使这种探索更加动态和方便的好方法,同时也不容易出错。如果你需要与不习惯编辑Python代码的人分享笔记本,widget可以成为一个救星,真正帮助数据变得生动。

仅仅阅读这些小工具还不如运行例子和自己使用它们来得有趣。试一试这些例子,然后在你自己的笔记本中尝试使用部件。

The postHow to use ipywidgets to make your Jupyter notebook interactiveappeared first onwrighters.io.