作者 | Will Koehrsen
翻譯 | Lemon
譯文出品 | Python資料之道 (ID:PyDataRoad)
本文通過一個項目案例,詳細的介紹了如何從 Bokeh 基礎到建構 Bokeh 互動式應用程式的過程,内容循序漸進且具有很高的實用性。本文共有兩萬字左右,屬于純幹貨分享,強烈推薦大家閱讀後續内容。
如果覺得内容不錯,歡迎關注『Python資料之道』并将内容分享到您的朋友圈。
本文由以下幾個大的部分組成:
- Bokeh 基礎介紹
- 在 Bokeh 中添加主動互動功能
- 在 Bokeh 中建立互動式可視化應用程式
Tips:
本文源代碼位址,可以在公衆号『Python資料之道』背景回複 “code” 來擷取。
關于 Bokeh 基礎的詳細介紹,可以參考以下内容:
- Bokeh入門
- figure 詳細解讀
- Bokeh基礎可視化圖形
- 資料類型簡介: ColumnDataSource
- 資料的添加、修改和篩選
- 圖形與元件的布局簡介
可用于資料科學的資源正在迅速發展,這在可視化領域尤其明顯,似乎每周都有另一種選擇。 随着所有這些進步,有一個共同的趨勢:增加互動性。 人們喜歡在靜态圖中檢視資料,但他們更喜歡的是使用資料來檢視更改參數如何影響結果。 關于我的研究,一份報告告訴建築物所有者他們可以通過改變他們的空調(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 庫,制作互動式圖表變得更加容易,并且能夠以引人注目的方式呈現資料科學成果。
本文的源代碼,請在公号『Python資料之道』背景回複 “code” 來擷取。
關于 Bokeh 基礎介紹的更多内容,可以檢視一下文章内容:
- Bokeh入門
- figure 詳細解讀
- Bokeh基礎可視化圖形
- 資料類型簡介: ColumnDataSource
- 資料的添加、修改和篩選
- 圖形與元件的布局簡介
本文來源
作者: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