散景:在 ColumnDataSource

时间:2021-03-26 15:23:09

标签: python bokeh

我有一个关于 Bokeh 2.3.0 Server 应用程序中的 ColumnDataSource 的问题。
下面是一个试图说明我的问题的例子。虽然它有点长,但我花了很多精力让它尽可能少但完整。

因此,我知道至少有两种主要的方法可以编辑 ColumnDataSource 中的数据。
第一个是通过使用“index_way”(我不知道如何正确调用此方法)通过使用 source.data['my_column_name'][<numpy_like_array_indexing>] = 'my_new_value' 其中 <numpy_like_array_indexing> 可能会导致类似 {{1 }} 或 [0:10] 等。像numpy数组一样对数据进行子集化。这样,例如可以使用 [[True,False,True]] 来索引数据。

第二种方法是使用 source.selected.indices.patch() function。参考调用将其描述为在特定位置有效更新数据源列

我在代码中遇到的第三种方法是编辑/更改 ColumnDataSource 中的完整列,例如 ColumnDataSource。这样,我可以将数据列设置为已经存在的数据列。

我的问题是:它们的设计行为是否有所不同?我发现使用“索引”方法的更改不会传播或更新到 HoverTool,对于其他两种方法,这似乎有效。
在以下代码示例中可以看到此行为。当更改图中的前几个样本时,通过使用选择工具选择它们并通过 source.data['my_data_column_1'] = source.data['my_data_column_2'] 编辑 source.data['Label'],HoverTool 不会显示“标签”的正确和更新值。但是,数据的更改是实际执行的,这可以通过 label_selected_via_index() 访问并打印 check_label() 的前几个样本看到。
将鼠标悬停在数据上时,使用其他方法之一更改 source.data['Label'] 值确实会显示正确且更新的值。

Label

在我的应用程序中,我有大量数据并观察到,使用 import pandas as pd from bokeh.plotting import figure, curdoc from bokeh.models import ColumnDataSource, LinearColorMapper, Dropdown, Button, HoverTool from bokeh.layouts import layout import random import time plot_data = 'Value1' LEN = 1000 df = pd.DataFrame({"ID":[i for i in range(LEN)], "Value1":[random.random() for i in range(LEN)], "Value2":[random.random() for i in range(LEN)], "Color": [int(random.random()*10) for i in range(LEN)] }) df['plot_data'] = df[plot_data] df['Label'] = "No Label Set" df['Label_new_col'] = "Label was added" source = ColumnDataSource(df) cmap = LinearColorMapper(palette="Turbo256", low = 0, high = 3) def make_tooltips(): return [('ID', '@ID'), ('Label', '@Label'), (plot_data, f'@{plot_data}')] tooltips = make_tooltips() hover_tool = HoverTool(tooltips=tooltips) plot1 = figure(plot_width=800, plot_height=250, tooltips=tooltips, tools='box_select') plot1.add_tools(hover_tool) circle = plot1.circle(x='ID', y='plot_data', source=source, fill_color={"field":'Color', "transform":cmap}, line_color={"field":'Color', "transform":cmap}) def update_plot_data(event): global plot_data plot_data = event.item source.data['plot_data'] = source.data[plot_data] hover_tool.tooltips = make_tooltips() dropdown = Dropdown(label='Change Value', menu=["Value1","Value2"]) dropdown.on_click(update_plot_data) def label_selected_via_index(event): t0 = time.time() selected = source.selected.indices source.data['Label'][0:10] = 'Label was added' hover_tool.tooltips = make_tooltips() source.selected.indices = [] print(f"Time needed for label_selected_via_index: {time.time()-t0:.5f}") button_set_label1 = Button(label='Set Label via Index') button_set_label1.on_click(label_selected_via_index) def label_selected_via_patch(event): t0 = time.time() selected = source.selected.indices patches = [(ind, 'Label was added') for ind in selected] source.patch({'Label': patches}) hover_tool.tooltips = make_tooltips() source.selected.indices = [] print(f"Time needed for label_selected_via_patch: {time.time()-t0:.5f}") button_set_label2 = Button(label='Set Label via Patch') button_set_label2.on_click(label_selected_via_patch) def label_selected_via_new_col(event): t0 = time.time() selected = source.selected.indices source.data['Label'] = source.data['Label_new_col'] hover_tool.tooltips = make_tooltips() source.selected.indices = [] print(f"Time needed for label_selected_via_new_col: {time.time()-t0:.5f}") button_set_label3 = Button(label='Set Label via New Column ') button_set_label3.on_click(label_selected_via_new_col) def check_label(event): print(f"first 10 labels: {[l for l in source.data['Label'][0:10]]}") button_label_check = Button(label='Check Label') button_label_check.on_click(check_label) layout_ = layout([[plot1], [dropdown], [button_set_label1 ,button_set_label2, button_set_label3], [button_label_check]]) curdoc().add_root(layout_) 确实比索引版本或替换完整列花费的时间要长得多。在我的应用程序中,索引方法需要不到一毫秒,而补丁方法需要超过一秒,这使得交互更改值时一切都变得更加滞后。基本上,我的应用程序在某种程度上类似于上面关于在一个图中选择样本并通过多个按钮分配标签的过程的示例。这些标签也通过工具提示显示在多重图中,所以这次更新对我来说是必要的。

有没有办法 A) 使索引版本也更新 Hovertool?我更喜欢这种方法,因为它在视觉上更快,或者 B) 以某种方式使 .patch() 版本更快?

我希望我能以某种方式使我的问题变得可以理解并感谢您的任何建议。

1 个答案:

答案 0 :(得分:1)

在 Bokeh 服务器应用程序的上下文中,值得牢记的是,“要在浏览器中显示更改,实际上需要发生什么?”答案大致如下:

  • 在 Python 中检测到(或发出信号)更改
  • 更改事件被序列化并通过网络发送到浏览器
  • 更改事件由 BokehJS 反序列化
  • 应用更改并更新浏览器中的视图

几乎散景总是处理最后三个步骤(以任何实际错误或待定功能为模)。所以问题真的归结为“有什么方法可以向 Bokeh 发出改变信号”? 让我们从描述可用和预期的位置开始(而不是从差异或不预期开始)。

直接分配给属性

为了在浏览器中看到变化而更新 Bokeh 对象的首要方法是为 Bokeh 属性分配一个全新的值。如果你这样做,例如.prop_name = new_value,字面上包括“点”和“等号”,然后 Bokeh 可以自动神奇地检测到更改并将其发送到浏览器。以下是一些示例:

plot.title.text = "New title"  # updates the title
glyph.line_color = "red"       # change a glyph's line color
slider.value = 10              # sets a slider's value

以上示例都显示了基本的标量(字符串、数字)值,但这对于更复杂的值也同样适用。这种通用机制的另一个极其常见的例子是更新 .data

的整个 ColumnDataSource dict
source.data = {'x': [...], 'y': [...]} # new data for a glyph or table

这会更新 CDS 中的所有数据,例如线条字形可能会重新绘制自身。

根据您在做什么、您的数据大小等,更新整个 .data dict 可能很昂贵(由于序列化、反序列化、网络传输等)。因此,在特定情况下,还有一些其他方法可能更有效。

“就地”特殊情况

上面的区别特征是一切都是“整体”分配,即没有变异或就地修改。在少数情况下,Bokeh 可以自动神奇地处理可变值的就地更新。不用太在意,到目前为止,最重要的例子是通过在 ColumnDataSource 上使用标准 Python dict 索引分配在 .data 中设置一个单个新列:< /p>

source.data['x'] = [...]  # Bokeh will automatically handle this

这是您上面的第三种方法。它工作正常,但用于更新 CDS .data 字典中的列。此方法仅通过网络发送一列新数据。只要您只需要更新大型 CDS 中的一列或几列,它可能比分配新的整个 .data 值要快。


什么不起作用基本上是任何其他类型的变异就地分配:

source.data['x'][100:200] = [...] # Bokeh does not automatically handle this

这是您上面的第一个(“索引”)方法,它不是初学者。这种用法不会触发浏览器的任何变化。

TLDR 是将每个 CDS 序列包装在一些覆盖标准 getitem/setitem 机制的自定义类中只会使普通用法效率太低,并且无法进行权衡。散景不会自动神奇地注意到或对这样的变异分配做任何事情。 (如果您纯粹在 BokehJS JavaScript 端,那么您可以像这样进行就地分配,然后手动调用 change.emit() 以手动触发更新,但这仅 用于纯 JS 方面)。

用于优化案例的专用 API

认识到有时甚至将 CDS 更新限制为单个列仍然不够有效,因此将 patchstream 方法添加到 ColumnDataSource

这些方法适用于以下情况:

  • 我想在所有列的末尾附加几个新值,而不是再次发送所有数据。 (例如,用于有效地流式传输新股票行情或其他传感器数据)

  • 我想在我的大型时间序列或图像中间更新一些特定值,但只发送更新后的值而不重新发送其他所有内容。

这是您的第二种方法,相对于总数据大小而言,小更新通常要快得多。至于 patch 具体来说,你可以看到例如patch_app.py 中的 examples folder 示例:

enter image description here

此示例以 20Hz 的更新速率同时更新三个独立的散点图、多线图和图像图。签入版本的数据大小相当适中,但我通过将所有数据放大 10-100 倍,在本地再次对其进行了测试,它仍然保持不变。如果您看到与补丁不同的内容(即多秒更新时间),则需要完整的 Minimal Reproducing Example 进行实际调查。