
在之前的 Bokeh 教程中,我们介绍了如何迈出使用 Bokeh 的第一步以及如何创建基础的散点图和折线图。除了强大的数据可视化能力,Bokeh 还通过在 Python 与浏览器之间建立连接,让用户可以在自己的服务器上托管应用程序。
我们将先创建应用,并在 Bokeh 服务器上运行,以便与同事共享。要了解 Bokeh 服务器的工作原理,请查看Bokeh 文档。
Bokeh 服务器基础
Bokeh 会将对象(如图表、小部件、坐标轴,以及几乎所有您创建的内容)转换为 JSON 格式。
Bokeh 服务器会将您编写的 Python 代码转换为 JSON 文档。该 JSON 文档由 Bokeh 的客户端库(BokehJS)在 JavaScript 中渲染,用户即可在浏览器中查看应用程序。
Bokeh 的优势在于它处理了 JavaScript 部分。因此,您无需具备 JavaScript 的先验知识就能创建应用!下图概述了 Bokeh 服务器的工作方式。

如何构建并运行 Bokeh 应用
Bokeh 应用的一般结构为:
- 创建图表和小部件:这是创建 Bokeh 应用的第一步。您应预先确定要展示的图表。
- 定义回调函数:回调函数指示应用如何响应用户交互。例如,当您在时间序列图中选择不同的股票代码时,图表应使用新数据进行更新。
- 创建布局:布局有助于我们以有效且美观的方式呈现图表。
创建股票市场分析 Bokeh Web 应用
项目描述
本节将向您展示如何创建一个简单的 Web 应用并将其部署供金融分析师使用。
假设您在 Big Money Inc——一家领先的资产管理公司工作。经理听到同事们称赞您的 Python 技能,现在她希望您创建一个股票市场应用,供公司的金融分析师比较两只股票。经理认为,公司的金融分析师可以通过快速将任意股票与同一行业的相似股票进行比较来节省时间。例如,他们可以比较苹果与微软的股票,从而为客户的投资组合挑选最佳标的。比较应包含以下基础指标:
- 收盘价趋势图
- 相关性
- 均值(期望收益)
- 标准差(期望风险)
还应当能够以交互方式自定义分析区间。例如,用户应能比较过去一周、一个月、一年,或任意时间段内两只股票的表现。您还需确保应用对公司所有用户随时可用。这意味着需要使用云服务进行部署。
先决条件
- 对 Bokeh 的基本理解。
- 具备一定的 Python 知识。
- 安装我们在Bokeh 入门教程中列出的相关包。
股票市场 Web 应用概览
该 Web 应用将解决上述金融案例中的需求。应用允许用户选择两个股票代码,并可视化所选股票价格与收益的描述性统计(如均值与标准差)、相关性图以及价格趋势图。通过允许组合选择不同股票代码和不同时间区间(用于价格趋势图),实现交互性。应用将呈现如下所示:

创建股票市场 Web 应用 Bokeh 项目
1. 创建一个项目文件夹,并将其命名为 SelectStock。
2. 打开项目文件夹,创建名为 main.py 的文件。
3. 将股票市场数据的副本保存到项目文件夹中,命名为“all_stocks_5yr.csv”。
保存股票代码列表的副本。文件 ticker.py 包含股票代码列表。仅包含 505 个股票代码,因为将完整列表直接写入 main.py 会让代码臃肿。更合适的做法是将股票代码列表保存在单独文件中,按需调用。
在您喜欢的代码编辑器(如 Spyder、atom 或 Sublime Text)中打开 main.py 文件。
导入包
在 main.py 文件中输入以下代码。该代码导入创建 Web 应用所需的包和股票代码:
# Import the required packages
import pandas as pd
from functools import lru_cache
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, PreText, Select, Div
from bokeh.plotting import figure
# load the list of tickers
import tickers
tickers = tickers.tickers
读取并加载数据
编写一个函数,避免股票代码与自身进行比较:
# Create a function to ensure that a ticker is not compared to itself
def foo(val, lst):
return [x for x in lst if x != val]
Add the following code to read and load data for the webapp:
# A function to read and load data
# Note the use of @lru_cache() decorator is to cache the data and speed up reloads
@lru_cache()
def load_ticker(ticker):
data = pd.read_csv('all_stocks_5yr.csv', parse_dates=['date'])
data = data.set_index('date')
data = data[data['Name'] == ticker]
return pd.DataFrame({ticker: data.close, ticker+'_returns': data.close.diff()})
# A function to create a dataframe for the selected tickers
@lru_cache()
def get_data(t1, t2):
df1 = load_ticker(t1)
df2 = load_ticker(t2)
data = pd.concat([df1, df2], axis=1)
data = data.dropna()
data['t1'] = data[t1]
data['t2'] = data[t2]
data['t1_returns'] = data[t1+'_returns']
data['t2_returns'] = data[t2+'_returns']
return data
# Put data into a ColumnDataSource
source = ColumnDataSource(
data=dict(date=[], t1=[], t2=[], t1_returns=[], t2_returns=[]))
创建小部件
# set up widgets
stats = PreText(text='', width=500)
ticker1 = Select(value='AAPL', options=foo('GOOG', tickers))
ticker2 = Select(value='GOOG', options=foo('AAPL', tickers))
小部件允许用户选择第一只和第二只待比较的股票代码。选择股票代码后,Bokeh 即可创建图表。
创建图表
# set up plots
tools = 'pan,wheel_zoom,xbox_select,reset'
corr = figure(width=400, height=350,
tools='pan,wheel_zoom,box_select,reset', output_backend='webgl')
corr.diamond('t1_returns', 't2_returns', size=2, source=source, color="red",
selection_color="orange", alpha=0.6, nonselection_alpha=0.1, selection_alpha=0.4)
time_series1 = figure(width=900, height=200, tools=tools,
x_axis_type='datetime', active_drag="xbox_select", output_backend='webgl')
time_series1.line('date', 't1', source=source, color='orange')
time_series2 = figure(width=900, height=200, tools=tools,
x_axis_type='datetime', active_drag="xbox_select", output_backend='webgl')
time_series2.x_range = time_series1.x_range
time_series2.line('date', 't2', source=source, color='green')
在这段代码中,我们首先定义了伴随图表的工具。相关性图是第一个图表,使用 figure() 函数指定 width = 400 和 height = 350。相关性图使用菱形标记。
我们使用折线图元创建股票代码的时间序列图。为了便于比较,两个时间序列采用相同的范围。
# Define callbacks
def change_ticker1(attrname, old, new):
ticker2.options = foo(new, tickers)
update()
def change_ticker2(attrname, old, new):
ticker1.options = foo(new, tickers)
update()
def update(selected=None):
t1, t2 = ticker1.value, ticker2.value
df = get_data(t1, t2)
data = df[['t1', 't2', 't1_returns', 't2_returns']]
source.data = data
update_stats(df, t1, t2)
corr.title.text = '%s returns vs. %s returns' % (t1, t2)
time_series1.title.text, time_series2.title.text = t1, t2
def update_stats(data, t1, t2):
stats.text = str(data[[t1, t2, t1+'_returns', t2+'_returns']].describe())
ticker1.on_change('value', change_ticker1)
ticker2.on_change('value', change_ticker2)
def selection_change(attrname, old, new):
t1, t2 = ticker1.value, ticker2.value
data = get_data(t1, t2)
selected = source.selected.indices
if selected:
data = data.iloc[selected, :]
update_stats(data, t1, t2)
source.selected.on_change('indices', selection_change)
在回调部分,前两个函数分别定义了股票代码 1 和股票代码 2 的回调。
update() 函数会在用户选择不同的股票组合时更新图表;update_stats() 函数在用户选择新的一组股票代码时更新统计信息;selection_change() 函数在用户更新股票代码时更新整个应用。
设置布局
第一步是为应用添加一个标题,向用户提供如何与 Web 应用交互的指引。Div 函数允许您提供文本并定义高度和宽度,以及其他自定义设置。
# set up layout
# Add a title message to the app
div = Div(
text="""
<p>Select two Stocks to compare key Statistics:</p>
""",
width=900,
height=30,
)
# Create layouts
app_title = div
widgets = column(ticker1, ticker2, stats)
main_row = row(widgets, corr)
series = column(time_series1, time_series2)
layout = column(app_title, main_row, series)
应用布局组合使用了 column 和 row 两个函数。
首先,在顶部放置标题。主行位于其下方(主行包含描述性统计与相关性图)。再下一行包含第一张时间序列图(对应股票代码 1),最后一行包含第二张时间序列图。
初始化应用
此步骤在服务器调用时初始化 Web 应用。
# initialize the app
update()
curdoc().add_root(layout)
curdoc().title = "Compare Stocks"
部署 Web 应用
要测试您的 Web 应用,您可以先在本机上部署。为此,打开 Anaconda Prompt 或任意终端,进入包含项目文件夹“SelectStock”的目录。您可以输入以下代码:
cd "System path to where the SelectStock folder is contained"
例如:cd "C:\Users\... \SelectStock"
输入以下代码运行 Web 应用:
bokeh serve --show SelectStock
或者,您也可以进入 SelectStock 文件夹并运行 main.py 文件:
Bokeh serve --show main.py
这会在默认浏览器中启动 Web 应用,地址为:
http://localhost:5006/SelectStock 或
此时您正在使用 Bokeh Server 来运行并部署 Web 应用。
您也可以使用 Heroku 等服务来部署 Web 应用。
结语
在本教程中,您已经学习了在本机构建并部署自有 Bokeh 应用的基本方法。教程向您展示了如何将所学内容融会贯通,构建一个实用、有趣且具交互性的应用,激发用户兴趣。
在学习完一个概念或工具后,通过构建类似这样的项目,能够最快速地提升编程技能。这种实践路径有助于您真正掌握某个工具或技能,并向潜在雇主展示您的成果。
如果您想进一步学习 Bokeh,可以阅读这本书。或者更进一步,试试 DataCamp 的Bokeh 交互式可视化课程。课程详细介绍了 Bokeh 以及如何创建更高级的图表。您还可以继续深入,学习如何使用实时数据构建自己的 Bokeh Web 应用。