乾貨: 可視化項目實戰經驗分享,輕鬆玩轉 Bokeh (建議收藏)

做者 | Will Koehrsenhtml

翻譯 | Lemonpython

譯文出品 | Python數據之道 (ID:PyDataRoad)瀏覽器

本文經過一個項目案例,詳細的介紹瞭如何從 Bokeh 基礎到構建 Bokeh 交互式應用程序的過程,內容按部就班且具備很高的實用性。本文共有兩萬字左右,屬於純乾貨分享,強烈推薦你們閱讀後續內容。服務器

若是以爲內容不錯,歡迎關注『Python數據之道』並將內容分享到您的朋友圈。app

本文由如下幾個大的部分組成:框架

  1. Bokeh 基礎介紹
  2. 在 Bokeh 中添加主動交互功能
  3. 在 Bokeh 中建立交互式可視化應用程序

Tips:less

本文源代碼地址,能夠在公衆號『Python數據之道』後臺回覆 「code」 來獲取。ide

關於 Bokeh 基礎的詳細介紹,能夠參考如下內容:函數

可用於數據科學的資源正在迅速發展,這在可視化領域尤爲明顯,彷佛每週都有另外一種選擇。 隨着全部這些進步,有一個共同的趨勢:增長交互性。 人們喜歡在靜態圖中查看數據,但他們更喜歡的是使用數據來查看更改參數如何影響結果。 關於個人研究,一份報告告訴建築物全部者他們能夠經過改變他們的空調(AC)使用計劃表節省多少電力是很好的,可是給他們一個交互式圖表更有效,他們能夠選擇不一樣的使用計劃表,看看他們的選擇如何影響用電量。 最近,受到互動圖的趨勢和不斷學習新工具的渴望的啓發,我一直在使用 Bokeh,一個 Python 庫。 我爲個人研究項目構建的儀表板中顯示了 Bokeh 交互功能的一個示例,以下:工具

image1-能耗項目示例

雖然我不能分享這個項目背後的代碼,但我能夠經過一個使用公開數據構建徹底交互式 Bokeh 應用程序的例子。 本文將介紹使用 Bokeh 建立應用程序的整個過程。 首先,咱們將介紹 Bokeh 的基礎內容, 咱們將使用 nycflights13 數據集,該數據集記錄了 2013年超過 300,000 個航班。首先,咱們將專一於可視化單個變量,在這種狀況下,航班的到達延遲時間爲幾分鐘,咱們將從構造基本直方圖開始。

Bokeh 基礎

Bokeh 的主要概念是圖形一次構建一層。 咱們首先建立一個圖形(figure),而後在圖形中添加稱爲 圖形符號(glyphs) 的元素。 glyphs 能夠根據所需的用途呈現多種形狀:圓形(circles),線條(lines) ,補丁(patches),條形(bars),弧形(arcs)等。 讓咱們經過製做帶有正方形和圓形的基本圖表來講明 glyphs 的概念。 首先,咱們使用 figure 方法建立一個圖,而後經過調用適當的方法並傳入數據將咱們的 glyphs 附加到 figure 中。 最後,咱們展現了所作的圖表。

# bokeh basics
from bokeh.plotting import figure
from bokeh.io import show, output_notebook

# Create a blank figure with labels
p = figure(plot_width = 600, plot_height = 600,
           title = 'Example Glyphs',
           x_axis_label = 'X', y_axis_label = 'Y')

# Example data
squares_x = [1, 3, 4, 5, 8]
squares_y = [8, 7, 3, 1, 10]
circles_x = [9, 12, 4, 3, 15]
circles_y = [8, 4, 11, 6, 10]

# Add squares glyph
p.square(squares_x, squares_y, size = 12, color = 'navy', alpha = 0.6)
# Add circle glyph
p.circle(circles_x, circles_y, size = 12, color = 'red')

# Set to output the plot in the notebook
output_notebook()
# Show the plot
show(p)

圖示以下:

image2-Bokeh基礎圖

如今讓咱們開始展現航班延誤數據,在進入圖表以前,應該加載數據並對其進行簡要檢查:

# Read the data from a csv into a dataframe
flights = pd.read_csv('../data/flights.csv', index_col=0)
# Summary stats for the column of interest
flights['arr_delay'].describe()

out[]:
count    327346.000000
mean          6.895377
std          44.633292
min         -86.000000
25%         -17.000000
50%          -5.000000
75%          14.000000
max        1272.000000

上述統計數據提供了能夠用來決策的信息:共有 327,346 次航班,最短延誤時間爲-86 分鐘(意味着航班提早 86 分鐘),最長延遲時間爲 1272 分鐘,驚人的 21 小時! 75% 的分位數僅在 14 分鐘,所以咱們能夠假設超過 1000 分鐘的數字多是異常值(這並不意味着它們是非法的,只是極端的)。 下面將重點關注直方圖的 -60 分鐘到 +120 分鐘之間的延遲。

直方圖是單個變量的初始可視化的常見選擇,由於它顯示了數據的分佈。 x 位置是被稱爲區間(bins)的變量的值,而且每一個柱子的高度表示每一個區間中的數據點的計數(數量)。 在咱們的例子中,x 位置將表明以分鐘爲單位的到達延遲,高度是相應 bin 中的航班數量。 Bokeh 沒有內置的直方圖,可是咱們可使用 quad 來製做咱們本身的直方圖。

爲條形圖(bars)建立數據,咱們將使用 Numpy 的 histogram 函數來計算每一個指定 bin 中的數據點數。 咱們將使用 5 分鐘長度的時間間隔(bins),這意味着該功能將計算每五分鐘延遲間隔的航班數量。 生成數據後,咱們將其放在 Pandas 的 dataframe 中,以將全部數據保存在一個對象中。

"""Bins will be five minutes in width, so the number of bins
is (length of interval / 5). Limit delays to [-60, +120] minutes using the range."""
arr_hist, edges = np.histogram(flights['arr_delay'],
                               bins = int(180/5),
                               range = [-60, 120])
# Put the information in a dataframe
delays = pd.DataFrame({'arr_delay': arr_hist,
                       'left': edges[:-1],
                       'right': edges[1:]})

數據以下:

image3-flights數據

flights 列是從 leftright 的每一個延遲間隔內的航班數量。 從這裏開始,咱們能夠建立一個新的 Bokeh 圖形,並添加一個指定適當參數的 quad

# Create the blank plot
p = figure(plot_height = 600, plot_width = 600,
           title = 'Histogram of Arrival Delays',
           x_axis_label = 'Delay (min)]',
           y_axis_label = 'Number of Flights')

# Add a quad glyph
p.quad(bottom=0, top=delays['flights'],
       left=delays['left'], right=delays['right'],
       fill_color='red', line_color='black')

# Show the plot
show(p)

image4-Bokeh繪製直方圖

從上述圖表來看,咱們看到到達延遲幾乎正態分佈,右側有輕微的正偏斜或重尾。

固然,其實有更簡單的方法能夠在 Python 中建立基本直方圖,好比可使用幾行 matplotlib 代碼完成相同的結果。 可是,咱們想在 Bokeh 圖中添加直方圖並進行交互演示。

增長交互性

本文介紹的第一種交互方式是被動交互。 這些是讀者能夠採起的不會改變所顯示數據的動做。 這些被稱爲檢查員(inspectors),由於它們容許讀者更詳細地 「查看」 數據。 一個有用的檢查器是當用戶將鼠標懸停在數據點上時出現的提示工具,在 Bokeh 中稱爲 HoverTool 。

image5-HoverTool

爲了添加提示工具(tooltips),咱們須要將數據源從 dataframe 更改成 ColumnDataSource (CDS),這是 Bokeh 中的一個關鍵概念。 CDS 是一個專門用於繪圖的對象,包括數據以及多個方法和屬性。 CDS 容許咱們爲圖形添加註釋和交互性,而且能夠從pandas 的 dataframe 構建。 實際數據自己保存在可經過 CDS 的 data 屬性訪問的字典中。 在這裏,咱們從 dataframe 建立源代碼,並查看數據字典中與 dataframe 列對應的鍵。

# Import the ColumnDataSource class
from bokeh.models import ColumnDataSource
# Convert dataframe to column data source
src = ColumnDataSource(delays)
src.data.keys()

out:
dict_keys(['flights', 'left', 'right', 'index'])

當咱們使用 CDS 添加 glyphs 時,咱們傳入 CDS 做爲 source 參數並使用字符串引用列名:

# Add a quad glyph with source this time
p.quad(source = src, bottom=0, top='flights',
       left='left', right='right',
       fill_color='red', line_color='black')

注意代碼如何經過單個字符串而不是以前的 df ['column'] 格式引用特定數據列,例如'flights','left' 和 'right'。

Bokeh 中的 HoverTool

HoverTool 的語法起初可能看起來有些複雜,但經過練習它們很容易建立。 咱們將 HoverTool 實例做爲 Python 元組的 「tooltips」 列表傳遞,其中第一個元素是數據的標籤,第二個元素引用咱們想要突出顯示的特定數據。 咱們可使用 $ 引用圖表的任一屬性,例如 x 或 y 位置,或使用 @ 引用咱們數據源中的特定字段。 這可能聽起來有點使人困惑,因此這裏有一個 HoverTool 的例子:

# Hover tool referring to our own data field using @ and
# a position on the graph using $
h = HoverTool(tooltips = [('Delay Interval Left ', '@left'),
                          ('(x,y)', '($x, $y)')])

在這裏,咱們使用 @ 引用 ColumnDataSource 中的 left 數據字段(對應於原始 dataframe 的 'left' 列),並使用 $ 引用光標的(x,y)位置。 結果以下:

image06-HoverTool

(x,y)位置是圖表上鼠標的位置,對咱們的直方圖不是頗有幫助,由於咱們要找到給定條形中對應於條形頂部的航班數量。 爲了解決這個問題,咱們將改變咱們的 tooltip 實例以引用正確的列。 格式化提示工具中顯示的數據可能使人沮喪,所以我一般在 dataframe 中使用正確的格式建立另外一列。 例如,若是我但願個人提示工具顯示給定欄的整個間隔,我在 dataframe 中建立一個格式化的列:

# Add a column showing the extent of each interval
delays['f_interval'] = ['%d to %d minutes' % (left, right) for left, right in zip(delays['left'], delays['right'])]

而後,我將此 dataframe 轉換爲 ColumnDataSource 並在個人 HoverTool 調用中訪問此列。 下面的代碼使用懸停工具建立繪圖,引用兩個格式化的列並將工具添加到繪圖中:

# Create the blank plot
p = figure(plot_height = 600, plot_width = 600,
           title = 'Histogram of Arrival Delays',
           x_axis_label = 'Delay (min)]',
           y_axis_label = 'Number of Flights')

# Add a quad glyph with source this time
p.quad(bottom=0, top='flights', left='left', right='right', source=src,
       fill_color='red', line_color='black', fill_alpha = 0.75,
       hover_fill_alpha = 1.0, hover_fill_color = 'navy')

# Add a hover tool referring to the formatted columns
hover = HoverTool(tooltips = [('Delay', '@f_interval'),
                             ('Num of Flights', '@f_flights')])

# Style the plot
p = style(p)

# Add the hover tool to the graph
p.add_tools(hover)

# Show the plot
show(p)

在 Bokeh 樣式中,經過將元素添加到原始圖形中來包含元素。 注意在 p.quad 調用中,還有一些額外的參數,hover_fill_alphahover_fill_color,當將鼠標懸停在條形圖上時會改變 glyph 的外觀。 我還使用 style 函數添加了樣式。 當使用樣式時,我會保持簡單並專一於標籤的可讀性。 圖的主要觀點是顯示數據,添加沒必要要的元素只會減小圖形的用處! 最終的圖形以下:

image07-帶HoverTool的直方圖

當將鼠標懸停在不一樣的欄上時,會獲得該欄的精確統計數據,顯示該區間內的間隔和航班數。 若是咱們爲圖形感到自豪,能夠將其保存到html文件中進行分享:

# Import savings function
from bokeh.io import output_file
# Specify the output file and save
output_file('hist.html')
show(p)

上面這張圖完成了工做,但它不是很吸引人! 讀者能夠看到航班延誤的分佈接近正態分佈(略有正偏斜),但他們沒有理由再花費更多的時間來分析該圖。

若是想要建立更具吸引力的可視化圖表,咱們能夠容許用戶經過交互本身來探索數據。 例如,在直方圖中,一個有價值的特徵是可以選擇特定航空公司進行比較,或者選擇更改 bins 的寬度以更精細地檢查數據。 幸運的是,這些都是可使用 Bokeh 在現有繪圖之上添加的功能。 直方圖的初始開發可能彷佛涉及一個簡單的繪圖,但如今咱們看到使用像 Bokeh 這樣強大的庫的回報!

在 Bokeh 中添加主動交互

Bokeh中有兩類交互:被動交互和主動交互。 前面介紹的被動交互也稱爲檢查器(inspectors),由於它們容許用戶更詳細地查閱圖表中的信息,但不會更改顯示的信息。 一個示例是當用戶將鼠標懸停在數據點上時顯示的提示信息,以下:

image10-被動交互

第二類交互稱爲主動交互,由於它會更改繪圖上顯示的實際數據。 這能夠是從選擇數據子集(例如特定航空公司)到改變多項式迴歸擬合自由度的任何事情。 Bokeh 中有多種類型的主動交互,但在這裏咱們將重點關注所謂的「小部件」(「widgets」),能夠點擊的元素,並讓用戶控制圖形的某些方面。

image11-主動交互

當查看圖表時,我喜歡使用主動交互,由於它們容許我本身探索數據。 我發現從我本身的數據(來自設計師的某個方向)而不是從徹底靜態的圖表中發現數據的結論更具洞察力。 此外,爲用戶提供必定的自由度使他們可以略微不一樣的解釋,從而產生有關數據集的有益討論。

主動互動的實現方法

一旦咱們開始添加主動交互,咱們須要超越單行代碼並進入封裝特定操做的函數。 對於 Bokeh 小部件(widgets)交互,有三個主要功能要實現:

  • make_dataset(): 按特定格式整理要顯示的特定數據
  • make_plot(): 使用指定的數據繪圖
  • update(): 根據用戶選擇更新繪圖

整理數據

在製做繪圖以前,須要設計將要顯示的數據。 對於交互式直方圖,將爲用戶提供三個可控參數:

  1. 航空公司 (在代碼中稱爲 carriers)
  2. 延遲的時間範圍,好比: -60 至 +120 分鐘
  3. 直方圖的寬度(即 bin 大小),默認值爲 5 分鐘

對於爲繪圖建立數據集的函數,咱們須要容許指定每一個參數。 爲了告知咱們如何在make_dataset 函數中轉換數據,咱們能夠加載全部相關數據並進行檢查。

image12-加載數據

在此數據集中,每行是一個單獨的航班。 arr_delay 列是以分鐘爲單位的航班到達延遲(負數表示航班早到)。 從前面的描述中咱們知道有 327,236 個航班,最小延遲爲 -86 分鐘,最大延遲爲 +1272 分鐘。 在 make_dataset 函數中,咱們但願根據 dataframe 中的 name 列選擇航空公司,並經過 arr_delay 列限制航班數量。

爲了生成直方圖的數據,咱們使用 numpy 中的 histogram 函數來計算每一個bin中的數據點數。在示例中,這是每一個指定延遲間隔內的航班數量。 在前面內容中,爲全部航班製做了直方圖,但如今咱們將針對每一個航空公司進行。 因爲每一個航空公司的航班數量差別很大,咱們能夠按比例顯示延遲,而不是原始計數。 也就是說,圖上的高度表示的是,在相應的 bin 區間,特定航空公司中該航班相對應於全部航班的延遲比例。 爲了從計數到比例,咱們將計數除以該航空公司的航班總數。

下面是製做數據集的完整代碼,該函數接收咱們想要包括的航空公司列表,要繪製的最小和最大延遲,以及以分鐘爲單位的指定 bin 寬度。

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

    # Check to make sure the start is less than the end!
    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
    # Iterate through all the carriers
    for i, carrier_name in enumerate(carrier_list):

        # Subset to the carrier
        subset = flights[flights['name'] == carrier_name]

        # Create a histogram with specified bins and range
        arr_hist, edges = np.histogram(subset['arr_delay'],
                                       bins = int(range_extent / bin_width),
                                       range = [range_start, range_end])

        # Divide the counts by the total to get a proportion and create df
        arr_df = pd.DataFrame({'proportion': arr_hist / np.sum(arr_hist),
                               'left': edges[:-1], 'right': edges[1:] })

        # Format the proportion
        arr_df['f_proportion'] = ['%0.5f' % proportion for proportion in arr_df['proportion']]

        # Format the interval
        arr_df['f_interval'] = ['%d to %d minutes' % (left, right) for left,
                                right in zip(arr_df['left'], arr_df['right'])]

        # Assign the carrier for labels
        arr_df['name'] = carrier_name

        # Color each carrier differently
        arr_df['color'] = Category20_16[i]

        # Add to the overall dataframe
        by_carrier = by_carrier.append(arr_df)

    # Overall dataframe
    by_carrier = by_carrier.sort_values(['name', 'left'])  
    # Convert dataframe to column data source
    return ColumnDataSource(by_carrier)

上述運行結果以下:

image13-整理好的數據

提醒一下,咱們使用 Bokeh 中 quad 函數來製做直方圖,所以咱們須要提供該圖形符號的左、右和頂部(底部將固定爲0)參數。 它們分別位於 「left」,「right」 和 「proportion」 列中。 color 列爲每一個顯示的航空公司提供了惟一的顏色,f_ 列爲 tooltips 提供了格式化文本。

下一個要實現的功能是 make_plot 。 該函數應該採用 ColumnDataSource(Bokeh中用於繪圖的特定類型的對象)並返回繪圖對象:

def make_plot(src):
        # Blank plot with correct labels
        p = figure(plot_width = 700, plot_height = 700,
                  title = 'Histogram of Arrival Delays by Carrier',
                  x_axis_label = 'Delay (min)', y_axis_label = 'Proportion')

        # Quad glyphs to create a histogram
        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')

        # Hover tool with vline mode
        hover = HoverTool(tooltips=[('Carrier', '@name'),
                                    ('Delay', '@f_interval'),
                                    ('Proportion', '@f_proportion')],
                          mode='vline')

        p.add_tools(hover)

        # Styling
        p = style(p)

        return p

若是咱們導入全部航空公司的數據,繪製的圖形以下:

image14-全部航線的延遲圖

這個直方圖很是混亂,由於有 16 家航空公司在同一圖表上繪製! 若是想比較航空公司,因爲信息重疊,這幾乎是不可能的。 幸運的是,咱們能夠添加小部件(widgets)以使繪圖更清晰並實現快速比較。

建立交互的小部件

一旦咱們在 Bokeh 中建立基本圖形,經過窗口小部件添加交互相對簡單。 咱們想要的第一個小部件是一個選擇框,容許讀者選擇要顯示的航空公司。 該控件將是一個複選框,容許根據須要進行儘量多的選擇,並在 Bokeh 中稱爲 「CheckboxGroup」 。 爲了製做選擇工具,咱們導入 CheckboxGroup 類並使用兩個參數來建立一個實例:labels 是想要在每一個框旁邊顯示的值和 active:初始選擇的值。 如下是包括全部航空公司的 CheckboxGroup 的代碼。

from bokeh.models.widgets import CheckboxGroup
# Create the checkbox selection element, available carriers is a  
# list of all airlines in the data
carrier_selection = CheckboxGroup(labels=available_carriers,
                                  active = [0, 1])

image15-CheckboxGroup

Bokeh 複選框中的標籤必須是字符串,而活動值是整數。 這意味着在圖形中 'AirTran Airways Corporation' 對應數字 0 ,'Alaska Airlines Inc.' 對應數值 1。 當想要將所選複選框與航空公司匹配時,須要確保查找與所選整數活動值關聯的字符串名稱。 咱們可使用小部件的 .labels.active 屬性來作到這一點:

# Select the airlines names from the selection values
[carrier_selection.labels[i] for i in carrier_selection.active]


out:
['AirTran Airways Corporation', 'Alaska Airlines Inc.']

製做複選的小部件後,須要將選定的航空公司複選框連接到圖表上顯示的信息。 這是使用 CheckboxGroup 的 .on_change 方法和咱們定義的 update 函數完成的。 update 函數老是有三個參數:attroldnew 並根據選擇控件更新繪圖。 咱們更改圖表上顯示的數據的方法是改變咱們傳遞給 make_plot 函數中的 glyph(s) 的數據源。 這可能聽起來有點抽象,因此這裏是有一個 update 函數的例子,它改變了直方圖以顯示所選的航空公司:

# Update function takes three default parameters
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 a new dataset based on the selected carriers and the
    # make_dataset function defined earlier
    new_src = make_dataset(carriers_to_plot,
                           range_start = -60,
                           range_end = 120,
                           bin_width = 5)
    # Update the source used in the quad glpyhs
    src.data.update(new_src.data)

在這裏,咱們將檢查基於 CheckboxGroup 中所選航空公司顯示的航空公司列表。 此列表將傳遞給 make_dataset 函數,該函數返回一個新的列數據源。 咱們經過調用 src.data.update 並重新數據源傳入數據來更新 glyphs 中使用的源的數據。 最後,爲了將 carrier_selection 小部件中的更改連接到 update 函數,咱們必須使用 .on_change 方法(稱爲事件處理程序)。

# Link a change in selected buttons to the update function
carrier_selection.on_change('active', update)

只要選擇或取消選擇不一樣的航空公司,就會調用更新功能。 最終結果是在直方圖上僅繪製了與所選航空公司相對應的圖形 ,以下所示:

image16-交互圖

更多的交互式控制

如今咱們知道了建立控件的基本工做流程,能夠添加更多元素。 每次,咱們建立窗口小部件,編寫更新函數以更改繪圖上顯示的數據,並使用事件處理程序將更新功能連接到窗口小部件。 咱們甚至能夠經過重寫函數來從多個元素中使用相同的更新函數,以從小部件中提取須要的值。 爲了練習,咱們將添加兩個額外的控件:一個 Slider,用於選擇直方圖的 bin 寬度;一個 RangeSlider,用於設置要顯示的最小和最大延遲。 如下是製做這些小部件和新的 update 函數的代碼:

# Slider to select the binwidth, value is selected number
binwidth_select = Slider(start = 1, end = 30,
                     step = 1, value = 5,
                     title = 'Delay Width (min)')
# Update the plot when the value is changed
binwidth_select.on_change('value', update)

# RangeSlider to change the maximum and minimum values on histogram
range_select = RangeSlider(start = -60, end = 180, value = (-60, 120),
                           step = 5, title = 'Delay Range (min)')

# Update the plot when the value is changed
range_select.on_change('value', update)


# Update function that accounts for all 3 controls
def update(attr, old, new):

    # Find the selected carriers
    carriers_to_plot = [carrier_selection.labels[i] for i in carrier_selection.active]

    # Change binwidth to selected value
    bin_width = binwidth_select.value

    # Value for the range slider is a tuple (start, end)
    range_start = range_select.value[0]
    range_end = range_select.value[1]

    # Create new ColumnDataSource
    new_src = make_dataset(carriers_to_plot,
                           range_start = range_start,
                           range_end = range_end,
                           bin_width = bin_width)

    # Update the data on the plot
    src.data.update(new_src.data)

標準的 slider 和 range slider 以下所示:

image17-滑動塊

除了使用更新功能顯示的數據以外,還能夠更改繪圖的其餘方面。例如,要更改標題文本以匹配 bin 寬度,能夠執行如下操做:

# Change plot title to match selection
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
# Put controls in a single element
controls = WidgetBox(carrier_selection, binwidth_select, range_select)

# Create a row layout
layout = row(controls, p)

# Make a tab with the layout
tab = Panel(child=layout, title = 'Delay Histogram')
tabs = Tabs(tabs=[tab])

我將整個佈局放在一個選項卡上,當咱們完成一個完整的應用程序時,咱們能夠將每一個繪圖放在一個單獨的選項卡上。 全部這些工做的最終結果以下:

image18-帶選項卡的交互圖

在 Bokeh 中建立交互式可視化應用程序

接下來將重點介紹 Bokeh 應用程序的結構,而不是繪圖細節,但後續會提供全部內容的完整代碼。咱們將繼續使用 NYCFlights13 數據集,這是 2013年 紐約 3 個機場的航班的真實航班信息集合。

要本身運行完整的應用程序,首先請確保安裝了Bokeh(使用pip install bokeh)。

其次,請在公衆號『Python數據之道』後臺回覆 「code」,獲取本項目的源代碼地址,而後從該地址中下載 bokeh_app.zip 文件夾,解壓縮,打開目錄中的命令窗口,而後鍵入 bokeh serve --show bokeh_app 。 這將設置一個本地 Bokeh 服務器並在瀏覽器中打開該應用程序。

最終的產品

在進入細節以前,讓咱們來看看咱們的目標是什麼,這樣能夠看到這些產品是如何組合在一塊兒的。 如下是一個簡短的剪輯,展現了咱們如何與整個儀表板進行交互:

在這裏,我在瀏覽器中使用 Bokeh 應用程序(在 Chrome 的全屏模式下),該應用程序在本地服務器上運行。 在頂部,咱們看到許多選項卡,每一個選項卡包含應用程序的不一樣部分。 儀表板的初衷是,雖然每一個選項卡能夠獨立存在,但咱們能夠將它們中的許多鏈接在一塊兒,以便可以完整地探索數據。 該視頻顯示了咱們可使用 Bokeh 製做的圖表範圍,從直方圖和密度圖,到咱們能夠按列排序的數據表,再到徹底交互式地圖。 除了咱們能夠在 Bokeh 中建立的圖形範圍以外,使用 Bokeh 庫的另外一個好處是交互。 每一個選項卡都有一個交互元素,使用戶能夠訪問數據並進行本身的發現。 根據經驗,在探索數據集時,人們喜歡本身探索,咱們能夠容許他們經過各類控制選擇和篩選數據。

如今咱們已經瞭解了咱們的目標,讓咱們來看看如何建立一個 Bokeh 應用程序。 強烈建議您本身下載代碼來運行(在公衆號『Python數據之道』後臺回覆 「code」,獲取本項目的源代碼地址)!

Bokeh 應用程序的文件結構

在編寫任何代碼以前,爲咱們的應用程序創建一個框架很重要。 在任何項目中,很容易被代碼帶走,很快就會丟失在一堆半完成的腳本和不合適的數據文件中,所以咱們但願事先爲咱們全部的代碼和數據建立一個結構。 該結構將幫助咱們跟蹤應用程序中的全部元素,並在出現不可避免的錯誤時協助調試。 此外,咱們能夠將此框架從新用於將來的項目,所以咱們在規劃階段的初始投資將得到回報。

要設置 Bokeh 應用程序,我建立一個父目錄來保存名爲 bokeh_app 的全部內容。 在這個目錄中,咱們將有一個數據子目錄(稱爲 data),咱們腳本的子目錄(scripts)和一個 main.py 腳本將全部內容整合到一塊兒。 一般,爲了管理全部代碼,我發現最好將每一個選項卡的代碼保存在單獨的 Python 腳本中,並從單個主腳本中調用它們。 如下是我用於 Bokeh 應用程序的文件結構,該文件結構改編自官方文檔。

bokeh_app
|
+--- data
|   +--- info.csv
|   +--- info2.csv
|
+--- scripts
|   +--- plot.py
|   +--- plot2.py
|
+--- main.py

對於此次咱們分析的航班程序項目,文件結構遵循通常大綱,以下:

image20-航班程序項目結構

在一個 bokeh_app 目錄下有三個主要部分:datascriptsmain.py。 當運行服務器時,咱們告訴 Bokeh 服務於 bokeh_app 目錄,它將自動搜索並運行 main.py 腳本。 有了通常的結構,讓咱們來看看 main.py ,這就是我喜歡稱之爲 Bokeh 應用程序的執行者!

主程序文件 (main.py)

main.py 腳本就像一個 Bokeh 應用程序的執行程序。 它加載數據,將其傳遞給其餘腳本,返回結果圖,並將它們組織到一個顯示中。 這將是我完整展現的惟一腳本,由於它對應用程序尤爲重要。

# Pandas for data management
import pandas as pd

# os methods for manipulating paths
from os.path import dirname, join

# Bokeh basics
from bokeh.io import curdoc
from bokeh.models.widgets import Tabs


# Each tab is drawn by one script
from scripts.histogram import histogram_tab
from scripts.density import density_tab
from scripts.table import table_tab
from scripts.draw_map import map_tab
from scripts.routes import route_tab

# Using included state data from Bokeh for map
from bokeh.sampledata.us_states import data as states

# Read data into dataframes
flights = pd.read_csv(join(dirname(__file__), 'data', 'flights.csv'),          index_col=0).dropna()

# Formatted Flight Delay Data for map
map_data = pd.read_csv(join(dirname(__file__), 'data', 'flights_map.csv'),
                            header=[0,1], index_col=0)

# Create each of the tabs
tab1 = histogram_tab(flights)
tab2 = density_tab(flights)
tab3 = table_tab(flights)
tab4 = map_tab(map_data, states)
tab5 = route_tb(flights)

# Put all the tabs into one application
tabs = Tabs(tabs = [tab1, tab2, tab3, tab4, tab5])

# Put the tabs in the current document for display
curdoc().add_root(tabs)

咱們從必要的導入開始,包括製做選項卡的函數,每一個函數都存儲在 scripts 目錄中的單獨腳本中。 若是查看文件結構,請注意 scripts 目錄中有一個 __init __.py 文件。 這是一個徹底空白的文件,須要放在目錄中,以便咱們使用相對語句導入相應的函數(例如 from scripts.histogram import histogram_tab)。 我不太肯定爲何須要它,可是它有效。

在 Python 庫和腳本導入以後,咱們在Python __file__ 屬性的幫助下讀取必要的數據。 在這種狀況下,咱們使用兩個 pandas dataframe( flightsmap_data)以及 Bokeh 中包含的美國各州的數據。 一旦讀入數據,腳本就會進行委託:它將適當的數據傳遞給每一個函數,每一個函數都繪製並返回一個選項卡,主腳本將全部這些選項卡組織在一個名爲 tabs 的佈局中。 做爲每一個單獨的選項卡函數的功能示例,讓咱們看一下繪製 map_tab 的函數。

此函數包含 map_data(航班數據的格式化版本)和美國各州的數據,併爲選定的航空公司生成航班路線圖:

image21-航班圖

def map_tab(map_data, states):
    ...
    def make_dataset(airline_list):
    ...
       return new_src
    def make_plot(src):
    ...
       return p

   def update(attr, old, new):
   ...
      new_src = make_dataset(airline_list)
      src.data.update(new_src.data)

   controls = ...
   tab = Panel(child = layout, title = 'Flight Map')
   return tab

咱們看到熟悉的 make_datasetmake_plotupdate 函數用於繪製帶有交互式控件的圖。 一旦咱們設置了繪圖,最後一行將整個繪圖返回到主腳本。 每一個單獨的腳本(5個選項卡中有5個)遵循相同的模式。

接下來返回主腳本,最後一步是收集選項卡並將它們添加到單個文檔中。

# Put all the tabs into one application
tabs = Tabs(tabs = [tab1, tab2, tab3, tab4, tab5])
# Put the tabs in the current document for display
curdoc().add_root(tabs)

選項卡顯示在應用程序的頂部,就像任何瀏覽器中的選項卡同樣,咱們能夠輕鬆地在它們之間切換以探索數據。

image22-帶選項卡的交互圖

運行 Bokeh 服務器

在製做繪圖所需的全部設置和代碼編寫完成以後,在本地運行 Bokeh 服務器很是簡單。 咱們打開一個命令行界面(我更喜歡 Git Bash, 但任何一個均可以工做),切換到包含 bokeh_app 的目錄並運行 bokeh serve --show bokeh_app 。 假設一切都正確,應用程序將在咱們的瀏覽器中自動打開地址 http:// localhost:5006 / bokeh_app 。 而後咱們能夠訪問該應用程序並瀏覽咱們的儀表板,效果以下:

image23-程序運行後的動態圖

在 Jupyter Notebook 中進行調試

若是出現問題(由於毫無疑問,咱們最初幾回編寫儀表板),必須中止服務器,更改文件,而後從新啓動服務器以查看咱們的更改是否具備所需效果,這可能會使人沮喪。 爲了快速迭代和解決問題,我一般在 Jupyter Notebook 中開發。 Jupyter Notebook 是 Bokeh 開發的理想環境,由於您能夠在 notebook 中建立和測試徹底交互式的圖形。 語法略有不一樣,可是一旦你有一個完整的繪圖,代碼只須要稍加修改,而後能夠複製並粘貼到一個獨立的 .py 腳本中。

要了解這一點,請查看用於開發應用程序的 Jupyter Notebook (請在公號『Python數據之道』後臺回覆 「code」,找到本項目的源代碼地址,獲取相應的 Jupyter Notebook 代碼文件)。

總結

徹底交互式的 Bokeh 儀表板使任何數據科學項目都脫穎而出。 一般狀況下,我看到個人同事作了不少很棒的統計工做,但卻未能清楚地傳達結果,這意味着全部工做都沒有獲得應有的承認。 從我的經驗來看,我也看到了 Bokeh 應用程序在傳達結果方面的有效性。 雖然製做完整的儀表板須要作不少工做,但結果是值得的。 此外,一旦咱們有了一個應用程序,能夠將該框架從新用於其餘項目。

從這個項目中,咱們能夠總結出幾個關鍵點,以適用於許多相似的數據科學項目:

  1. 在開始數據科學任務(Bokeh 或其餘任何東西)以前,擁有適當的框架/結構相當重要。 這樣,你就不會發現本身迷失在試圖查找錯誤的代碼的泥潭中。 此外,一旦咱們開發出一個有效的框架,它能夠用最少的努力重複使用。
  2. 找到一個容許您快速迭代思路的調試工具相當重要。 編寫代碼 - 查看結果 - 修復錯誤,這種循環在 Jupyter Notebook 能夠實現高效的開發(尤爲是對於小規模項目)。
  3. Bokeh 中的交互式應用程序將提高您的項目並鼓勵用戶參與。 儀表板能夠是一個獨立的探索項目,或突出您已經完成的全部艱難的分析工做!
  4. 估計你永遠不知道在哪裏能夠找到你將在工做或輔助項目中使用的下一個工具。 因此,不要懼怕嘗試新的軟件和技術!

以上是本文的所有內容,經過像 Bokeh 和 plot.ly 這樣的 Python 庫,製做交互式圖表變得更加容易,而且可以以引人注目的方式呈現數據科學成果。

本文的源代碼,請在公號『Python數據之道』後臺回覆 「code」 來獲取。

關於 Bokeh 基礎介紹的更多內容,能夠查看一下文章內容:

本文來源

做者:Will Koehrsen

Data Visualization with Bokeh in Python, Part I: Getting Started

Data Visualization with Bokeh in Python, Part II: Interactions

Data Visualization with Bokeh in Python, Part III: Making a Complete Dashboard

相關文章
相關標籤/搜索