Python Bokeh:在ColumnDataSource中编辑数据的某些方法之间有什么区别

Python Bokeh:在ColumnDataSource中编辑数据的某些方法之间有什么区别,python,bokeh,Python,Bokeh,我有一个关于Bokeh 2.3.0服务器应用程序中的ColumnDataSource的问题。 下面是一个例子,试图说明我的问题。虽然它要长一点,但我已经花了很多努力使它尽可能的小但尽可能的完整 因此,我知道在ColumnDataSource中编辑数据至少有两种主要的方法是可行的。 第一个是通过使用源代码使用“index\u方式”(我不知道如何正确调用此方法)。数据['my\u column\u name'][]='my\u new\u value',其中可能导致类似[0:10]或[[True,F

我有一个关于Bokeh 2.3.0服务器应用程序中的
ColumnDataSource
的问题。
下面是一个例子,试图说明我的问题。虽然它要长一点,但我已经花了很多努力使它尽可能的小但尽可能的完整

因此,我知道在
ColumnDataSource
中编辑数据至少有两种主要的方法是可行的。
第一个是通过使用
源代码使用“index\u方式”(我不知道如何正确调用此方法)。数据['my\u column\u name'][]='my\u new\u value'
,其中
可能导致类似
[0:10]
[[True,False,True]
,等等。将数据子集为numpy数组。通过这种方式,可以使用
源.selected.index
对数据进行索引

second方法使用的是
ColumnDataSource
的。引用调用将其描述为在特定位置高效更新数据源列

我在代码中遇到的第三种方法是在
ColumnDataSource
中编辑/更改一个完整的列,比如
source.data['my\u data\u column\u 1']=source.data['my\u data\u column\u 2']
。这样,我可以将数据列设置为已经存在的数据列

我的问题是:它们的设计是否有不同的表现?我发现使用“index”方法的更改不会传播或更新到HoverTool,而对于其他两种方法,这似乎有效。
在下面的代码示例中可以看到这种行为。更改绘图中的前几个样本时,通过使用选择工具选择样本并通过
source.data['Label']
via
Label\u selected\u via\u index()
编辑,鼠标悬停工具不会显示“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_)
在我的应用程序中,我收集了大量数据并观察到,使用
.patch()
确实比索引版本或替换完整列花费的时间要长得多。在我的应用程序中,索引方法需要不到一毫秒的时间,而补丁方法需要超过一秒钟的时间,这使得在交互更改值时,所有内容都有点滞后。基本上,我的应用程序在某种程度上类似于上面的示例,关于在一个绘图中选择样本并通过多个按钮分配标签的过程。这些标签也会通过工具提示显示在多重打印中,因此我需要进行此更新

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


我希望我能以某种方式理解我的问题,并感谢任何建议。

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

  • 在Python中检测到(或发出信号)更改
  • 更改事件被序列化并通过网络发送到浏览器
  • 更改事件由BokehJS反序列化
  • 将应用更改并更新浏览器中的视图
几乎所有的Bokeh都会处理最后三个步骤(模块化任何实际的bug或TBD特性)。所以问题真的可以归结为“用什么方式向博克发出改变的信号?”? 让我们从描述什么是可用的和有意的(而不是从差异或无意的)开始

财产的直接转让 更新Bokeh对象以查看浏览器中的更改的第一个主要方法是为Bokeh特性指定一个全新的值。如果您这样做,例如,
.prop_name=new_value
,字面上包括“点”和“等号”,则Bokeh可以自动神奇地检测到更改并将其发送到浏览器。以下是几个例子:

plot.title.text=“新建标题”#更新标题
glyph.line_color=“red”#更改glyph的线条颜色
slider.value=10#设置滑块的值
上面的示例都显示了基本的标量(字符串、数字)值,但这同样适用于更复杂的值。这种通用机制的另一个非常常见的例子是更新
列数据源的整个
.data
目录

source.data={'x':[…],'y':[…]}#字形或表格的新数据
这会更新CD中的所有数据,例如,线条图示符可能会重新绘制自身

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

“就地”特殊情况 上面的区别特征是,一切都是一个“整体”分配,即没有突变或就地修改。在少数情况下,Bokeh可以自动神奇地处理对可变值的就地更新。不必太深究,到目前为止,最重要的例子是在
ColumnDataSource
中设置一个新列,方法是在
上使用标准的Python dict索引赋值

source.data['x']=[…]#Bokeh将自动处理此问题
这是您上面的第三种方法。它可以正常工作,但仅用于更新CD
.data
dict中的列