[譯] 利用 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索