掌握這幾點,輕鬆玩轉 Bokeh 可視化 (項目實戰經驗分享)
- 2019 年 10 月 8 日
- 筆記
本文轉自公眾號『Python數據之道』
本文通過一個項目案例,詳細的介紹了如何從 Bokeh 基礎到構建 Bokeh 互動式應用程式的過程,內容循序漸進且具有很高的實用性。本文共有兩萬字左右,屬於純乾貨分享,強烈推薦大家閱讀後續內容。
如果覺得內容不錯,歡迎關注『Python數據之道』並將內容分享到您的朋友圈。
本文由以下幾個大的部分組成:
- Bokeh 基礎介紹
- 在 Bokeh 中添加主動交互功能
- 在 Bokeh 中創建互動式可視化應用程式
Tips: 本文源程式碼地址,可以在公眾號『Python數據之道』後台回復 「code」 來獲取。 如需對文章進行轉載,請在後台回復 「轉載」, 聯繫授權事宜。 關於 Bokeh 基礎的詳細介紹,可以參考以下內容: Bokeh: 入門 | figure | 基礎圖形 | CDS | 數據篩選 | 圖形布局
可用於數據科學的資源正在迅速發展,這在可視化領域尤其明顯,似乎每周都有另一種選擇。 隨著所有這些進步,有一個共同的趨勢:增加交互性。 人們喜歡在靜態圖中查看數據,但他們更喜歡的是使用數據來查看更改參數如何影響結果。
關於我的研究,一份報告告訴建築物所有者他們可以通過改變他們的空調(AC)使用計劃表節省多少電力是很好的,但是給他們一個互動式圖表更有效,他們可以選擇不同的使用計劃表,看看他們的選擇如何影響用電量。
最近,受到互動圖的趨勢和不斷學習新工具的渴望的啟發,我一直在使用 Bokeh,一個 Python 庫。 我為我的研究項目構建的儀錶板中顯示了 Bokeh 交互功能的一個示例,如下:

雖然我不能分享這個項目背後的程式碼,但我可以通過一個使用公開數據構建完全互動式 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)
圖示如下:

現在讓我們開始展示航班延誤數據,在進入圖表之前,應該載入數據並對其進行簡要檢查:
# 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:]})
數據如下:

flights
列是從 left
到 right
的每個延遲間隔內的航班數量。 從這裡開始,我們可以創建一個新的 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)

從上述圖表來看,我們看到到達延遲幾乎正態分布,右側有輕微的正偏斜或重尾。
當然,其實有更簡單的方法可以在 Python 中創建基本直方圖,比如可以使用幾行 matplotlib 程式碼完成相同的結果。 但是,我們想在 Bokeh 圖中添加直方圖並進行交互演示。
增加交互性
本文介紹的第一種交互方式是被動交互。 這些是讀者可以採取的不會改變所顯示數據的動作。 這些被稱為檢查員(inspectors),因為它們允許讀者更詳細地 「查看」 數據。 一個有用的檢查器是當用戶將滑鼠懸停在數據點上時出現的提示工具,在 Bokeh 中稱為 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)位置。 結果如下:

(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_alpha
和 hover_fill_color
,當將滑鼠懸停在條形圖上時會改變 glyph 的外觀。
我還使用 style
函數添加了樣式。 當使用樣式時,我會保持簡單並專註於標籤的可讀性。 圖的主要觀點是顯示數據,添加不必要的元素只會減少圖形的用處! 最終的圖形如下:

當將滑鼠懸停在不同的欄上時,會得到該欄的精確統計數據,顯示該區間內的間隔和航班數。 如果我們為圖形感到自豪,可以將其保存到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),因為它們允許用戶更詳細地查閱圖表中的資訊,但不會更改顯示的資訊。 一個示例是當用戶將滑鼠懸停在數據點上時顯示的提示資訊,如下:

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

當查看圖表時,我喜歡使用主動交互,因為它們允許我自己探索數據。 我發現從我自己的數據(來自設計師的某個方向)而不是從完全靜態的圖表中發現數據的結論更具洞察力。 此外,為用戶提供一定的自由度使他們能夠略微不同的解釋,從而產生有關數據集的有益討論。
主動互動的實現方法
一旦我們開始添加主動交互,我們需要超越單行程式碼並進入封裝特定操作的函數。 對於 Bokeh 小部件(widgets)交互,有三個主要功能要實現:
- make_dataset(): 按特定格式整理要顯示的特定數據
- make_plot(): 使用指定的數據繪圖
- update(): 根據用戶選擇更新繪圖
整理數據
在製作繪圖之前,需要設計將要顯示的數據。 對於互動式直方圖,將為用戶提供三個可控參數:
- 航空公司 (在程式碼中稱為 carriers)
- 延遲的時間範圍,比如: -60 至 +120 分鐘
- 直方圖的寬度(即 bin 大小),默認值為 5 分鐘
對於為繪圖創建數據集的函數,我們需要允許指定每個參數。 為了告知我們如何在 make_dataset
函數中轉換數據,我們可以載入所有相關數據並進行檢查。

在此數據集中,每行是一個單獨的航班。 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)
上述運行結果如下:

提醒一下,我們使用 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
如果我們導入所有航空公司的數據,繪製的圖形如下:

這個直方圖非常混亂,因為有 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])

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 函數總是有三個參數: attr
, old
, new
並根據選擇控制項更新繪圖。 我們更改圖表上顯示的數據的方法是改變我們傳遞給 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)
只要選擇或取消選擇不同的航空公司,就會調用更新功能。 最終結果是在直方圖上僅繪製了與所選航空公司相對應的圖形 ,如下所示:

更多的互動式控制
現在我們知道了創建控制項的基本工作流程,可以添加更多元素。 每次,我們創建窗口小部件,編寫更新函數以更改繪圖上顯示的數據,並使用事件處理程式將更新功能鏈接到窗口小部件。 我們甚至可以通過重寫函數來從多個元素中使用相同的更新函數,以從小部件中提取需要的值。
為了練習,我們將添加兩個額外的控制項:一個 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 如下所示:

除了使用更新功能顯示的數據之外,還可以更改繪圖的其他方面。例如,要更改標題文本以匹配 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_dataset
, make_plot
和 update
來根據控制項和小部件本身改變繪圖。 我們通過定義布局將所有這些元素連接到一個頁面上。
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])
我將整個布局放在一個選項卡上,當我們完成一個完整的應用程式時,我們可以將每個繪圖放在一個單獨的選項卡上。 所有這些工作的最終結果如下:

三、在 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
對於這次我們分析的航班程式項目,文件結構遵循一般大綱,如下:

在一個 bokeh_app
目錄下有三個主要部分: data
, scripts
和 main.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( flights
和 map_data
)以及 Bokeh 中包含的美國各州的數據。
一旦讀入數據,腳本就會進行委託:它將適當的數據傳遞給每個函數,每個函數都繪製並返回一個選項卡,主腳本將所有這些選項卡組織在一個名為 tabs
的布局中。 作為每個單獨的選項卡函數的功能示例,讓我們看一下繪製 map_tab
的函數。
此函數包含 map_data
(航班數據的格式化版本)和美國各州的數據,並為選定的航空公司生成航班路線圖:

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_dataset
, make_plot
和 update
函數用於繪製帶有互動式控制項的圖。 一旦我們設置了繪圖,最後一行將整個繪圖返回到主腳本。 每個單獨的腳本(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)
選項卡顯示在應用程式的頂部,就像任何瀏覽器中的選項卡一樣,我們可以輕鬆地在它們之間切換以探索數據。

運行 Bokeh 伺服器
在製作繪圖所需的所有設置和程式碼編寫完成之後,在本地運行 Bokeh 伺服器非常簡單。 我們打開一個命令行介面(我更喜歡 Git Bash, 但任何一個都可以工作),切換到包含 bokeh_app
的目錄並運行 bokeh serve --show bokeh_app
。
假設一切都正確,應用程式將在我們的瀏覽器中自動打開地址 http:// localhost:5006 / bokeh_app
。 然後我們可以訪問該應用程式並瀏覽我們的儀錶板,效果如下:

在 Jupyter Notebook 中進行調試
如果出現問題(因為毫無疑問,我們最初幾次編寫儀錶板),必須停止伺服器,更改文件,然後重新啟動伺服器以查看我們的更改是否具有所需效果,這可能會令人沮喪。
為了快速迭代和解決問題,我通常在 Jupyter Notebook 中開發。 Jupyter Notebook 是 Bokeh 開發的理想環境,因為您可以在 notebook 中創建和測試完全互動式的圖形。 語法略有不同,但是一旦你有一個完整的繪圖,程式碼只需要稍加修改,然後可以複製並粘貼到一個獨立的 .py
腳本中。
要了解這一點,請查看用於開發應用程式的 Jupyter Notebook (請在公號『Python 數 據 之 道』後台回復 「code」,找到本項目的源程式碼地址,獲取相應的 Jupyter Notebook 程式碼文件)。
總結
完全互動式的 Bokeh 儀錶板使任何數據科學項目都脫穎而出。 通常情況下,我看到我的同事做了很多很棒的統計工作,但卻未能清楚地傳達結果,這意味著所有工作都沒有得到應有的認可。
從個人經驗來看,我也看到了 Bokeh 應用程式在傳達結果方面的有效性。 雖然製作完整的儀錶板需要做很多工作,但結果是值得的。 此外,一旦我們有了一個應用程式,可以將該框架重新用於其他項目。
從這個項目中,我們可以總結出幾個關鍵點,以適用於許多類似的數據科學項目:
- 在開始數據科學任務(Bokeh 或其他任何東西)之前,擁有適當的框架/結構至關重要。 這樣,你就不會發現自己迷失在試圖查找錯誤的程式碼的泥潭中。 此外,一旦我們開發出一個有效的框架,它可以用最少的努力重複使用。
- 找到一個允許您快速迭代思路的調試工具至關重要。 編寫程式碼 – 查看結果 – 修復錯誤,這種循環在 Jupyter Notebook 可以實現高效的開發(尤其是對於小規模項目)。
- Bokeh 中的互動式應用程式將提升您的項目並鼓勵用戶參與。 儀錶板可以是一個獨立的探索項目,或突出您已經完成的所有艱難的分析工作!
- 估計你永遠不知道在哪裡可以找到你將在工作或輔助項目中使用的下一個工具。 所以,不要害怕嘗試新的軟體和技術!
以上是本文的全部內容,通過像 Bokeh 和 plot.ly 這樣的 Python 庫,製作互動式圖表變得更加容易,並且能夠以引人注目的方式呈現數據科學成果。
Tips: 本文源程式碼地址,可以在公眾號後台回復 「bokeh」 來獲取。 關於 Bokeh 基礎的詳細介紹,可以參考以下內容: Bokeh: 入門 | figure | 基礎圖形 | CDS | 數據篩選 | 圖形布局
本文來源: 作者:Will Koehrsen Data Visualization with Bokeh in Python, Part I: Getting Started https://towardsdatascience.com/data-visualization-with-bokeh-in-python-part-one-getting-started-a11655a467d4 Data Visualization with Bokeh in Python, Part II: Interactions https://towardsdatascience.com/data-visualization-with-bokeh-in-python-part-ii-interactions-a4cf994e2512 Data Visualization with Bokeh in Python, Part III: Making a Complete Dashboard https://towardsdatascience.com/data-visualization-with-bokeh-in-python-part-iii-a-complete-dashboard-dc6a86aa6e23
