2. 整理数据
在制作绘图之前,需要设计将要显示的数据。 对于交互式直方图,将为用户提供三个可控参数:
航空公司 (在代码中称为 carriers)
延迟的时间范围,比如: -60 至 +120 分钟
直方图的宽度(即 bin 大小),默认值为 5 分钟
对于为绘图创建数据集的函数,我们需要允许指定每个参数。 为了告知我们如何在 make_dataset函数中转换数据,我们可以加载所有相关数据并进行检查。
在此数据集中,每行是一个单独的航班。 arr_delay列是以分钟为单位的航班到达延迟(负数表示航班早到)。 从前面的描述中我们知道有 327,236 个航班,最小延迟为 -86 分钟,最大延迟为 +1272 分钟。
在 make_dataset函数中,我们希望根据 dataframe 中的 name列选择航空公司,并通过 arr_delay 列限制航班数量。
为了生成直方图的数据,我们使用 numpy 中的 histogram函数来计算每个bin中的数据点数。在示例中,这是每个指定延迟间隔内的航班数量。 在前面内容中,为所有航班制作了直方图,但现在我们将针对每个航空公司进行。
由于每个航空公司的航班数量差异很大,我们可以按比例显示延迟,而不是原始计数。 也就是说,图上的高度表示的是,在相应的 bin 区间,特定航空公司中该航班相对应于所有航班的延迟比例。 为了从计数到比例,我们将计数除以该航空公司的航班总数。
下面是制作数据集的完整代码,该函数接收我们想要包括的航空公司列表,要绘制的最小和最大延迟,以及以分钟为单位的指定 bin 宽度。
defmake_dataset(carrier_list, range_start = -60, range_end = 120, bin_width = 5):
# Check to make sure the start is less than the end!
assertrange_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
fori, carrier_name inenumerate(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 forproportion inarr_df[ 'proportion']]
# Format the interval
arr_df[ 'f_interval'] = [ '%d to %d minutes'% (left, right) forleft,
right inzip(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
returnColumnDataSource(by_carrier)
上述运行结果如下:
提醒一下,我们使用 Bokeh 中 quad函数来制作直方图,因此我们需要提供该图形符号的左、右和顶部(底部将固定为0)参数。 它们分别位于 “left”,“right” 和 “proportion” 列中。 color 列为每个显示的航空公司提供了唯一的颜色, f_列为 tooltips 提供了格式化文本。
下一个要实现的功能是 make_plot。 该函数应该采用 ColumnDataSource(Bokeh中用于绘图的特定类型的对象)并返回绘图对象:
defmake_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)
returnp
如果我们导入所有航空公司的数据,绘制的图形如下:
这个直方图非常混乱,因为有 16 家航空公司在同一图表上绘制! 如果想比较航空公司,由于信息重叠,这几乎是不可能的。 幸运的是,我们可以添加小部件(widgets)以使绘图更清晰并实现快速比较。
创建交互的小部件
一旦我们在 Bokeh 中创建基本图形,通过窗口小部件添加交互相对简单。 我们想要的第一个小部件是一个选择框,允许读者选择要显示的航空公司。 该控件将是一个复选框,允许根据需要进行尽可能多的选择,并在 Bokeh 中称为 “CheckboxGroup” 。
为了制作选择工具,我们导入 CheckboxGroup类并使用两个参数来创建一个实例: labels是想要在每个框旁边显示的值和 active:初始选择的值。 以下是包括所有航空公司的 CheckboxGroup的代码。
frombokeh.models.widgets importCheckboxGroup
# 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] fori incarrier_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
defupdate(attr, old, new):
# Get the list of carriers for the graph
carriers_to_plot = [carrier_selection.labels[i] fori incarrier_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)
只要选择或取消选择不同的航空公司,就会调用更新功能。 最终结果是在直方图上仅绘制了与所选航空公司相对应的图形 ,如下所示:
2. 更多的交互式控制
现在我们知道了创建控件的基本工作流程,可以添加更多元素。 每次,我们创建窗口小部件,编写更新函数以更改绘图上显示的数据,并使用事件处理程序将更新功能链接到窗口小部件。 我们甚至可以通过重写函数来从多个元素中使用相同的更新函数,以从小部件中提取需要的值。
为了练习,我们将添加两个额外的控件:一个 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
defupdate(attr, old, new):
# Find the selected carriers
carriers_to_plot = [carrier_selection.labels[i] fori incarrier_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 中还有许多其他类型的交互,但是现在,我们的三个控件允许用户在图表上“玩”很多!
3. 把它们放在一起
我们的互动图表的所有元素都已到位。 我们有三个必要的函数:make_dataset, make_plot和 update来根据控件和小部件本身改变绘图。 我们通过定义布局将所有这些元素连接到一个页面上。
frombokeh.layouts importcolumn, row, WidgetBox
frombokeh.models importPanel
frombokeh.models.widgets importTabs
# 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])
我将整个布局放在一个选项卡上,当我们完成一个完整的应用程序时,我们可以将每个绘图放在一个单独的选项卡上。 所有这些工作的最终结果如下:
03 在 Bokeh 中创建交互式可视化应用程序
接下来将重点介绍 Bokeh 应用程序的结构,而不是绘图细节,但后续会提供所有内容的完整代码。我们将继续使用 NYCFlights13 数据集,这是 2013年 纽约 3 个机场的航班的真实航班信息集合。
要自己运行完整的应用程序,首先请确保安装了Bokeh(使用 pip install bokeh)。
1. 最终的产品
在进入细节之前,让我们来看看我们的目标是什么,这样可以看到这些产品是如何组合在一起的。 以下是一个简短的剪辑,展示了我们如何与整个仪表板进行交互:
在这里,我在浏览器中使用 Bokeh 应用程序(在 Chrome 的全屏模式下),该应用程序在本地服务器上运行。 在顶部,我们看到许多选项卡,每个选项卡包含应用程序的不同部分。 仪表板的初衷是,虽然每个选项卡可以独立存在,但我们可以将它们中的许多连接在一起,以便能够完整地探索数据。 该视频显示了我们可以使用 Bokeh 制作的图表范围,从直方图和密度图,到我们可以按列排序的数据表,再到完全交互式地图。
除了我们可以在 Bokeh 中创建的图形范围之外,使用 Bokeh 库的另一个好处是交互。 每个选项卡都有一个交互元素,使用户可以访问数据并进行自己的发现。
根据经验,在探索数据集时,人们喜欢自己探索,我们可以允许他们通过各种控制选择和筛选数据。
现在我们已经了解了我们的目标,让我们来看看如何创建一个 Bokeh 应用程序。 强烈建议您自己下载代码来运行!
2. Bokeh 应用程序的文件结构
在编写任何代码之前,为我们的应用程序建立一个框架很重要。 在任何项目中,很容易被代码带走,很快就会丢失在一堆半完成的脚本和不合适的数据文件中,因此我们希望事先为我们所有的代码和数据创建一个结构。 该结构将帮助我们跟踪应用程序中的所有元素,并在出现不可避免的错误时协助调试。 此外,我们可以将此框架重新用于未来的项目,因此我们在规划阶段的初始投资将获得回报。
要设置 Bokeh 应用程序,我创建一个父目录来保存名为 bokeh_app的所有内容。 在这个目录中,我们将有一个数据子目录(称为 data),我们脚本的子目录(s)和一个 main.py脚本将所有内容整合到一起。
通常,为了管理所有代码,我发现最好将每个选项卡的代码保存在单独的 Python 脚本中,并从单个主脚本中调用它们。 以下是我用于 Bokeh 应用程序的文件结构,该文件结构改编自官方文档。
bokeh_app
|
+--- data
| +--- info.csv
| +--- info2.csv
|
+--- s
| +--- plot.py
| +--- plot2.py
|
+--- main.py
对于这次我们分析的航班程序项目,文件结构遵循一般大纲,如下:
在一个 bokeh_app目录下有三个主要部分: data, s和 main.py。 当运行服务器时,我们告诉 Bokeh 服务于 bokeh_app目录,它将自动搜索并运行 main.py脚本。 有了一般的结构,让我们来看看 main.py,这就是我喜欢称之为 Bokeh 应用程序的执行者!
3. 主程序文件 (main.py)
main.py脚本就像一个 Bokeh 应用程序的执行程序。 它加载数据,将其传递给其他脚本,返回结果图,并将它们组织到一个显示中。 这将是我完整展示的唯一脚本,因为它对应用程序尤其重要。
# Pandas for data management
importpandas aspd
# os methods for manipulating paths
fromos.path importdirname, join
# Bokeh basics
frombokeh.io importcurdoc
frombokeh.models.widgets importTabs
# Each tab is drawn by one
froms.histogram importhistogram_tab
froms.density importdensity_tab
froms.table importtable_tab
froms.draw_map importmap_tab
froms.routes importroute_tab
# Using included state data from Bokeh for map
frombokeh.sampledata.us_states importdata asstates
# 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)
我们从必要的导入开始,包括制作选项卡的函数,每个函数都存储在 s目录中的单独脚本中。 如果查看文件结构,请注意 s 目录中有一个 __init __.py文件。 这是一个完全空白的文件,需要放在目录中,以便我们使用相对语句导入相应的函数(例如 from s.histogram import histogram_tab)。 我不太确定为什么需要它,但是它有效。
在 Python 库和脚本导入之后,我们在Python __file__属性的帮助下读取必要的数据。 在这种情况下,我们使用两个 pandas dataframe( flights和 map_data)以及 Bokeh 中包含的美国各州的数据。
一旦读入数据,脚本就会进行委托:它将适当的数据传递给每个函数,每个函数都绘制并返回一个选项卡,主脚本将所有这些选项卡组织在一个名为 tabs的布局中。 作为每个单独的选项卡函数的功能示例,让我们看一下绘制 map_tab的函数。
此函数包含 map_data(航班数据的格式化版本)和美国各州的数据,并为选定的航空公司生成航班路线图:
defmap_tab(map_data, states):
...
defmake_dataset(airline_list):
...
returnnew_src
defmake_plot(src):
...
returnp
defupdate(attr, old, new):
...
new_src = make_dataset(airline_list)
src.data.update(new_src.data)
controls = ...
tab = Panel(child = layout, title = 'Flight Map')
returntab
我们看到熟悉的 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)
选项卡显示在应用程序的顶部,就像任何浏览器中的选项卡一样,我们可以轻松地在它们之间切换以探索数据。
4. 运行 Bokeh 服务器
在制作绘图所需的所有设置和代码编写完成之后,在本地运行 Bokeh 服务器非常简单。 我们打开一个命令行界面(我更喜欢 Git Bash, 但任何一个都可以工作),切换到包含 bokeh_app的目录并运行 bokeh serve --show bokeh_app。
假设一切都正确,应用程序将在我们的浏览器中自动打开地址 http:// localhost:5006/bokeh_app。 然后我们可以访问该应用程序并浏览我们的仪表板,效果如下:
5. 在 Jupyter Notebook 中进行调试
如果出现问题(因为毫无疑问,我们最初几次编写仪表板),必须停止服务器,更改文件,然后重新启动服务器以查看我们的更改是否具有所需效果,这可能会令人沮丧。
为了快速迭代和解决问题,我通常在 Jupyter Notebook 中开发。 Jupyter Notebook 是 Bokeh 开发的理想环境,因为您可以在 notebook 中创建和测试完全交互式的图形。 语法略有不同,但是一旦你有一个完整的绘图,代码只需要稍加修改,然后可以复制并粘贴到一个独立的 .py脚本中。
要了解这一点,请查看用于开发应用程序的 Jupyter Notebook。
04 总结
完全交互式的 Bokeh 仪表板使任何数据科学项目都脱颖而出。 通常情况下,我看到我的同事做了很多很棒的统计工作,但却未能清楚地传达结果,这意味着所有工作都没有得到应有的认可。
从个人经验来看,我也看到了 Bokeh 应用程序在传达结果方面的有效性。 虽然制作完整的仪表板需要做很多工作,但结果是值得的。 此外,一旦我们有了一个应用程序,可以将该框架重新用于其他项目。
从这个项目中,我们可以总结出几个关键点,以适用于许多类似的数据科学项目:
在开始数据科学任务(Bokeh 或其他任何东西)之前,拥有适当的框架/结构至关重要。 这样,你就不会发现自己迷失在试图查找错误的代码的泥潭中。 此外,一旦我们开发出一个有效的框架,它可以用最少的努力重复使用。
找到一个允许您快速迭代思路的调试工具至关重要。 编写代码 - 查看结果 - 修复错误,这种循环在 Jupyter Notebook 可以实现高效的开发(尤其是对于小规模项目)。
Bokeh 中的交互式应用程序将提升您的项目并鼓励用户参与。 仪表板可以是一个独立的探索项目,或突出您已经完成的所有艰难的分析工作!
估计你永远不知道在哪里可以找到你将在工作或辅助项目中使用的下一个工具。 所以,不要害怕尝试新的软件和技术!
以上是本文的全部内容,通过像 Bokeh 和 plot.ly 这样的 Python 库,制作交互式图表变得更加容易,并且能够以引人注目的方式呈现数据科学成果。
本文来源:
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
▼
Q:关于Bokeh你还有哪些使用技巧?
转载 / 投稿请联系:baiyu@hzbook.com返回搜狐,查看更多