[译] 利用 Python 中 Bokeh 实现数据可视化,第二部分:交互

超越静态图的图解html

本系列的第一部分 中,咱们介绍了在 Bokeh(Python 中一个强大的可视化库)中建立的一个基本柱状图。最后的结果显示了 2013 年从纽约市起飞的航班延迟到达的分布状况,以下所示(有一个很是好的工具提示):前端

这张表完成了任务,但并非很吸引人!用户能够看到航班延迟的几乎是正常的(有轻微的斜率),但他们没有理由在这个数字上花几秒钟以上的时间。python

若是咱们想建立更吸引人的可视化数据,能够容许用户经过交互方式来获取他们想要的数据。好比,在这个柱状图中,一个有价值的特性是可以选择指定航空公司进行比较,或者选择更改容器的宽度来更详细地检查数据。辛运的是,咱们可使用 Bokeh 在现有的绘图基础上添加这两个特性。柱状图的最初开发彷佛只涉及到了一个简单的图,但咱们如今即将体验到像 Bokeh 这样的强大的库的所带来的好处!android

本系列的全部代码均可在 GitHub 上得到。任何感兴趣的人均可以查看全部的数据清洗细节(数据科学中一个不那么鼓舞人心但又必不可少的部分),也能够亲自运行它们!(对于交互式 Bokeh 图,咱们仍然可使用 Jupyter Notebook 来显示结果,咱们也能够编写 Python 脚本,并运行 Bokeh 服务器。我一般使用 Jupyter Notebook 进行开发,由于它能够在不重启服务器的状况下,就能够很容易的快速迭代和更改绘图。而后我将它们迁移到服务器中来显示最终结果。你能够在 GitHub 上看到一个独立的脚本和完整的笔记)。ios

主动的交互

在 Bokeh 中,有两类交互:被动的和主动的。第一部分所描述的被动交互也称为 inspectors,由于它们容许用户更详细地检查一个图,但不容许更改显示的信息。好比,当用户悬停在数据点上时出现的工具提示:git

工具提示,被动交互器github

第二类交互被称为 active,由于它更改了显示在绘图上的实际数据。这能够是从选择数据的子集(例如指定的航空公司)到改变匹配多项式回归拟合程度中的任何数据。在 Bokeh 中有多种类型的 active 交互,但这里咱们将重点讨论“小部件”,能够被单击,并且用户可以控制某些绘图方面的元素。后端

小部件示例(下拉按钮和单选按钮组)浏览器

当我查看图时,我喜欢主动的交互(好比那些在 FlowingData 上的交互),由于它们容许我本身去研究数据。我发现让人印象更深入的是从我本身的数据中发现的结论(从设计者那里获取的一些研究方向),而不是从一个彻底静态的图表中发现的结论。此外,给予用户必定程度的自由,可让他们对数据集提出更有用的讨论,从而产生不一样的解释。服务器

交互概述

一旦咱们开始添加主动交互,咱们就须要越过单行代码,深刻封装特定操做的函数。对于 Bokeh 小部件的交互,有三个主要函数能够实现:

  • make_dataset() 格式化想要显示的特定数据
  • make_plot() 用指定的数据进行绘图
  • update() 基于用户选择来更新绘图

格式化数据

在咱们绘制这个图以前,咱们须要规划将要显示的数据。对于咱们的交互柱状图,咱们将为用户提供三个可控参数:

  1. 航班显示(在代码中称为运营商)
  2. 绘图中的时间延迟范围,例如:-60 到 120 分钟
  3. 默认状况下,柱状图的容器宽度是 5 分钟

对于生成绘图数据集的函数,咱们须要容许指定每一个参数。为了告诉咱们如何转换 make_dataset 函数中的数据,咱们须要加载全部相关数据进行检查。

柱状图数据

在此数据集中,每一行都是一个单独的航班。 arr_delay 列是航班到达延误数分钟(负数表示航班提早到达)。在第一部分中,咱们作了一些数据探索,知道有 327,236 次航班,最小延误时间为 - 86 分钟,最大延误时间为 1272 分钟。在 make_dataset 函数中,咱们想基于 dataframe 中的 name 列来选择公司,并用 arr_delay 列来限制航班。

为了生成柱状图的数据,咱们使用 numpy 函数 histogram 来统计每一个容器中的数据点数。在咱们的示例中,这是每一个指定延迟间隔中的航班数。对于第一部分,咱们作了一个包含全部航班的柱状图,但如今咱们会为每个运营商都提供一个柱状图。因为每一个航空公司的航班数目有很大差别,咱们能够显示延迟而不是按原始数目显示,能够按比例显示。也就是说,图上的高度对应于特定航空公司的全部航班比例,该航班在相应的容器中有延迟。从计数到比例,咱们除以航空公司的总数。

下面是生成数据集的完整代码。函数接受咱们但愿包含的运营商列表,要绘制的最小和最大延迟,以及制定的容器宽度(以分钟为单位)。

def make_dataset(carrier_list, range_start = -60, range_end = 120, bin_width = 5):

    # 为了确保起始点小于终点而进行检查
    assert range_start < range_end, "Start must be less than end!"
    
    by_carrier = pd.DataFrame(columns=['proportion', 'left', 'right', 
                                       'f_proportion', 'f_interval',
                                       'name', 'color'])
    range_extent = range_end - range_start
    
    # 遍历全部运营商
    for i, carrier_name in enumerate(carrier_list):

        # 运营商子集
        subset = flights[flights['name'] == carrier_name]

        # 建立具备指定容器和范围的柱状图
        arr_hist, edges = np.histogram(subset['arr_delay'], 
                                       bins = int(range_extent / bin_width), 
                                       range = [range_start, range_end])

        # 将极速除以总数,获得一个比例,并建立 df
        arr_df = pd.DataFrame({'proportion': arr_hist / np.sum(arr_hist), 
                               'left': edges[:-1], 'right': edges[1:] })

        # 格式化比例
        arr_df['f_proportion'] = ['%0.5f' % proportion for proportion in arr_df['proportion']]

        # 格式化间隔
        arr_df['f_interval'] = ['%d to %d minutes' % (left, right) for left, 
                                right in zip(arr_df['left'], arr_df['right'])]

        # 为标签指定运营商
        arr_df['name'] = carrier_name

        # 不一样颜色的运营商
        arr_df['color'] = Category20_16[i]

        # 添加到整个 dataframe 中
        by_carrier = by_carrier.append(arr_df)

    # 整体 dataframe
    by_carrier = by_carrier.sort_values(['name', 'left'])
    
    # 将 dataframe 转换为列数据源
    return ColumnDataSource(by_carrier)
复制代码

(我知道这是一篇关于 Bokeh 的博客,但在你不能在没有格式化数据的状况下来生成图表,所以我使用了相应的代码来演示个人方法!)

运行带有所需运营商的函数结果以下:

做为提醒,咱们使用 Bokeh quad 表来制做柱状图,所以咱们须要提供表的左、右和顶部(底部将固定为 0)。它们分别在罗列在 leftright 以及 proportion。颜色列为每一个运营商提供了惟一的颜色,f_ 列为工具提供了格式化文本的功能。

下一个要实现的函数是 make_plot。函数应该接受 ColumnDataSource (Bokeh 中用于绘图的一种特定类型对象)并返回绘图对象:

def make_plot(src):
        # 带有正确标签的空白图
        p = figure(plot_width = 700, plot_height = 700, 
                  title = 'Histogram of Arrival Delays by Carrier',
                  x_axis_label = 'Delay (min)', y_axis_label = 'Proportion')

        # 建立柱状图的四种符号
        p.quad(source = src, bottom = 0, top = 'proportion', left = 'left', right = 'right',
               color = 'color', fill_alpha = 0.7, hover_fill_color = 'color', legend = 'name',
               hover_fill_alpha = 1.0, line_color = 'black')

        # vline 模式下的悬停工具
        hover = HoverTool(tooltips=[('Carrier', '@name'), 
                                    ('Delay', '@f_interval'),
                                    ('Proportion', '@f_proportion')],
                          mode='vline')

        p.add_tools(hover)

        # Styling
        p = style(p)

        return p 
复制代码

若是咱们向全部航空公司传递一个源,此代码将给出如下绘图:

这个柱状图很是混乱,由于 16 家航空公司都绘制在同一张图上!由于信息被重叠了,因此若是咱们想比较航空公司就显得不太现实。辛运的是,咱们能够添加小部件来使绘制的图更清晰,也可以进行快速地比较。

建立可交互的小部件

一旦咱们在 Bokeh 中建立一个基础图形,经过小部件添加交互就相对简单了。咱们须要的第一个小部件是容许用户选择要显示的航空公司的选择框。这是一个容许根据须要进行尽量多的选择的复选框控件,在 Bokeh 中称为T CheckboxGroup.。为了制做这个可选工具,咱们须要导入 CheckboxGroup 类来建立带有两个参数的实例,labels:咱们但愿显示每一个框旁边的值以及 active:检查选中的初始框。如下建立的 CheckboxGroup 代码中附有所需的运营商。

from bokeh.models.widgets import CheckboxGroup

# 建立复选框可选元素,可用的载体是
# 数据中全部航空公司组成的列表
carrier_selection = CheckboxGroup(labels=available_carriers, 
                                  active = [0, 1])
复制代码

CheckboxGroup 部件

Bokeh 复选框中的标签必须是字符串,但激活值须要的是整型。这意味着在在图像 ‘AirTran Airways Corporation’ 中,激活值为 0,而 ‘Alaska Airlines Inc.’ 激活值为 1。当咱们想要将选中的复选框与 airlines 想匹配时,咱们须要确保所选的整型激活值能匹配与之对应的字符串。咱们可使用部件的 .labels.active 属性来实现。

# 从选择值中选择航空公司的名称
[carrier_selection.labels[i] for i in carrier_selection.active]

['AirTran Airways Corporation', 'Alaska Airlines Inc.']
复制代码

在制做完小部件后,咱们如今须要将选中的航空公司复选框连接到图表上显示的信息中。这是使用 CheckboxGroup 的 .on_change 方法和咱们定义的 update 函数完成的。update 函数老是具备三个参数:attr、old、new,并基于选择控件来更新绘图。改变图形上显示的数据的方式是改变咱们传递给 make_plot 函数中的图形的数据源。这听起来可能有点抽象,所以下面是一个 update 函数的示例,该函数经过更改柱状图来显示选定的航空公司:

# update 函数有三个默认参数
def update(attr, old, new):
    # Get the list of carriers for the graph
    carriers_to_plot = [carrier_selection.labels[i] for i in
                        carrier_selection.active]

    # 根据被选中的运营商和
    # 先前定义的 make_dataset 函数来建立一个新的数据集
    new_src = make_dataset(carriers_to_plot,
                           range_start = -60,
                           range_end = 120,
                           bin_width = 5)

    # update 在 quad glpyhs 中使用的源
    src.data.update(new_src.data)
复制代码

这里,咱们从 CheckboxGroup 中检索要基于选定航空公司显示的航空公司列表。这个列表被传递给 make_dataset 函数,它返回一个新的列数据源。咱们经过调用 src.data.update 以及传入来自新源的数据更新图表中使用的源数据。最后,为了将 carrier_selection 小部件中的更改连接到 update 函数,咱们必须使用 .on_change 方法(称为事件处理器)。

# 将选定按钮中的更改连接到 update 函数
carrier_selection.on_change('active', update)
复制代码

在选择或取消其余航班的时会调用 update 函数。最终结果是在柱状图中只绘制了与选定航空公司相对应的符号,以下所示:

更多控件

如今咱们已经知道了建立控件的基本工做流程,咱们能够添加更多元素。咱们每次建立小部件时,编写 update 函数来更改显示在绘图上的数据,经过事件处理器来将 update 函数连接到小部件。咱们甚至能够经过重写函数来从多个元素中使用相同的 update 函数来从小部件中提取咱们所需的值。在实践过程当中,咱们将添加两个额外的控件:一个用于选择柱状图容器宽度的 Slider,另外一个是用于设置最小和最大延迟的 RangeSlider。下面是生成这些小部件和 update 函数的代码:

# 滑动 bindwidth,对应的值就会被选中
binwidth_select = Slider(start = 1, end = 30, 
                     step = 1, value = 5,
                     title = 'Delay Width (min)')
# 当值被修改时,更新绘图
binwidth_select.on_change('value', update)

# RangeSlider 用于修改柱状图上的最小最大值
range_select = RangeSlider(start = -60, end = 180, value = (-60, 120),
                           step = 5, title = 'Delay Range (min)')

# 当值被修改时,更新绘图
range_select.on_change('value', update)


# 用于 3 个控件的 update 函数
def update(attr, old, new):
    
    # 查找选定的运营商
    carriers_to_plot = [carrier_selection.labels[i] for i in carrier_selection.active]
    
    # 修改 binwidth 为选定的值
    bin_width = binwidth_select.value

    # 范围滑块的值是一个元组(开始,结束)
    range_start = range_select.value[0]
    range_end = range_select.value[1]
    
    # 建立新的列数据
    new_src = make_dataset(carriers_to_plot,
                           range_start = range_start,
                           range_end = range_end,
                           bin_width = bin_width)

    # 在绘图上更新数据
    src.data.update(new_src.data)
复制代码

标准滑块和范围滑块以下所示:

只要咱们想,出了使用 update 函数显示数据以外,咱们也能够修改其余的绘图功能。例如,为了将标题文本与容器宽度匹配,咱们能够这样作:

# 将绘图标题修改成匹配选择
bin_width = binwidth_select.value
p.title.text = 'Delays with %d Minute Bin Width' % bin_width
复制代码

在 Bokeh 中海油许多其余类型的交互,但如今,咱们的三个控件容许运行在图标上“运行”!

把全部内容放在一块儿

咱们的全部交互式绘图元素都已经说完了。咱们有三个必要的函数:make_datasetmake_plotupdate,基于控件和系哦啊不见自身来更改绘图。咱们经过定义布局将全部这些元素链接到一个页面上。

from bokeh.layouts import column, row, WidgetBox
from bokeh.models import Panel
from bokeh.models.widgets import Tabs

# 将控件放在单个元素中
controls = WidgetBox(carrier_selection, binwidth_select, range_select)
    
# 建立行布局
layout = row(controls, p)
    
# 使用布局来建立一个选项卡
tab = Panel(child=layout, title = 'Delay Histogram')
tabs = Tabs(tabs=[tab])
复制代码

我将整个布局放在一个选项卡上,当咱们建立一个完整的应用程序时,咱们能够为每一个绘图都建立一个单独的选项卡。最后的工做结果以下所示:

能够在 GitHub 上查看相关代码,并绘制本身的绘图。

下一步和内容

本系列的下一部分将讨论如何使用多个绘图来制做一个完整的应用程序。咱们将经过服务器来展现咱们的工做结果,能够经过浏览器对其进行访问,并建立一个完整的仪表盘来探究数据集。

咱们能够看到,最终的互动绘图比原来的有用的多!咱们如今能够比较航空公司之间的延迟,并更改容器的宽度/范围,来了解这些分布是如何被影响的。增长的交互性提升了绘图的价值,由于它增长了对数据的支持,并容许用户经过本身的探索得出结论。尽管设置了初始化的绘图,但咱们仍然能够看到如何轻松地将元素和控件添加到现有的图形中。与像 matplotlib 这样快速简单的绘图库相比,使用更重的绘图库(好比 bokeh)能够定制化绘图和交互。不一样的可视化库有不一样的优势和用例,但当咱们想要增长交互的额外维度时,Bokeh 是一个很好的选择。但愿在这一点上,你有足够的信心来开发你本身的可视化绘图,也但愿看到你能够分享本身的创做。

欢迎向我反馈以及建设性的批评,能够在 Twitter @koehrsen_will 上和我联系。


  1. [译] 利用 Python中的 Bokeh 实现数据可视化,第一部分:入门
  2. [译] 利用 Python中的 Bokeh 实现数据可视化,第二部分:交互
  3. [译] 利用 Python中的 Bokeh 实现数据可视化,第三部分:制做一个完整的仪表盘

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索