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

作者 | Will Koehrsen

翻譯 | Lemon

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

本文通過一個項目案例,詳細的介紹瞭如何從 Bokeh 基礎到構建 Bokeh 交互式應用程序的過程,內容循序漸進且具有很高的實用性。本文共有兩萬字左右,屬於純乾貨分享,強烈推薦大家閱讀後續內容。

如果覺得內容不錯,歡迎關注『Python數據之道』並將內容分享到您的朋友圈。

本文由以下幾個大的部分組成:

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

Tips:

本文源代碼地址,可以在公衆號『Python數據之道』後臺回覆 “code” 來獲取。

關於 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章