两个交互式散景图:在一个图形中选择一个值,然后更改另一个图形

hor*_*hoe 3 python interactive bokeh

我想创建一个交互式python散景图。我有两个通过列名称链接的数据框。当我在plot1中选择一个条形时,我想在plot 2中显示属于该列的数据框2(df2)的数据。例如,df1可以包含df2所有列的平均值。如果单击显示的均值,则可以在第二张图中查找构成均值基础的原始数据。不幸的是,我无法使其正常运行,也找不到可比较的示例。以下是到目前为止的内容。我认为错误在其中mycolumn="@colnames",而taptool没有返回我期望的结果。 下面的源代码根据@bigreddot的评论进行了更新

import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, TapTool
from bokeh.plotting import figure
from bokeh.layouts import row
#from bokeh.plotting import show
from bokeh.io import curdoc

# data for plot 2
df2 = pd.DataFrame({"A" : np.linspace(10, 20, 10),
                    "B" : np.linspace(20, 30, 10),
                    "C" : np.linspace(30, 40, 10),
                    "D" : np.linspace(40, 50, 10),
                    "E" : np.linspace(50, 60, 10),})
source2 = ColumnDataSource(
        data=dict(
            x=list(df2.index.values),
            y=list(df2.iloc[:,0].values)
        )
    )

# data for plot 1
df1 = np.mean(df2)
source1 = ColumnDataSource(
        data=dict(
            x=list(range(0,df1.shape[0])),
            y=list(df1.values),
            colnames = list(df1.index.values)
        )
    )

# Plot graph one with data from df1 and source 1 as barplot
plot1 = figure(plot_height=300, plot_width=400, tools="tap")
plot1.vbar(x='x',top='y',source=source1, bottom=0,width =0.5)


# Plot graph two with data from df2 and source 2 as line
plot2 = figure(plot_height=300, plot_width=400, title="myvalues", 
              tools="crosshair,box_zoom,reset,save,wheel_zoom,hover")    
r1 = plot2.line(x='x',y='y',source =source2, line_alpha = 1, line_width=1)
# safe data from plot 2 for later change in subroutine
ds1 = r1.data_source

def update_plot2(mycolumn):
    try:
        ds1.data['y'] = df2[mycolumn].values
    except:   
        pass

# add taptool to plot1
taptool = plot1.select(type=TapTool)
taptool.callback = update_plot2(mycolumn="@colnames")

#show(row(plot1,plot2))
curdoc().add_root(row(plot1,plot2))
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

big*_*dot 5

您缺少一个基本概念。Bokeh实际上是两个库,Python Bokeh API和JavaScript BokehJS库,它们在浏览器中完成所有工作。这些部分可以通过两种方式进行交互:

  • 独立文件

    这些是散景服务器不支持的散景文档。它们可能具有许多工具和交互功能(例如,来自CustomJS回调),但它们是一种单向旅行,生成了独立的HTML,JavaScript和CSS,它们与任何Python运行时都没有进一步的联系。

  • 散景应用

    这些是由Bokeh服务器支持的Bokeh文档,并自动同步Python和JS状态。除了独立文档的所有功能之外,还可以将事件和工具连接到真实的Python回调,以在Bokeh服务器中执行该回调。

当您使用output_fileoutput_notebookshow上面所做的操作时,您将创建一个独立的 Bokeh文档。这意味着,一旦在浏览器中显示了文档,就根本不再与任何Python连接。特别是,这意味着您无法访问Pandas Dataframes或NumPy数组之类的内容,也无法在任何回调中使用Python代码,因为浏览器对这些或Python一点都不了解。您只能按照文档CustomJSJavaScript回调部分中所述使用回调。

如果您需要运行真实的Python代码来响应事件,选择,工具等,这是Bokeh服务器可以提供的。请参阅文档中的运行Bokeh服务器

根据您的数据大小,可以通过在Bokeh数据源中预先发送所有数据并在CustomJS其间进行切换的回调来完成Standlone文档所需的操作。


hor*_*hoe 5

最终,@ bigreddot帮助我从工具中找到了此Bokeh Server回调。在对我有用的代码下面:

import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import curdoc
from random import sample


# data for plot 2
df2 = pd.DataFrame({"A" : sample(np.linspace(10, 20, 10),5),
                    "B" : sample(np.linspace(20, 30, 10),5),
                    "C" : sample(np.linspace(30, 40, 10),5),
                    "D" : sample(np.linspace(40, 50, 10),5),
                    "E" : sample(np.linspace(50, 60, 10),5),})
source2 = ColumnDataSource(
        data=dict(
            x=list(df2.index.values),
            y=list(df2.iloc[:,0].values)
        )
    )

# data for plot 1
df1 = np.mean(df2)
source1 = ColumnDataSource(
        data=dict(
            x=list(range(0,df1.shape[0])),
            y=list(df1.values),
            colnames = list(df1.index.values)
        )
    )

# Plot graph one with data from df1 and source 1 as barplot
plot1 = figure(plot_height=300, plot_width=400, tools="tap")
barglyph = plot1.vbar(x='x',top='y',source=source1, bottom=0,width =0.5)


# Plot graph two with data from df2 and source 2 as line
plot2 = figure(plot_height=300, plot_width=400, title="myvalues", 
              tools="crosshair,box_zoom,reset,save,wheel_zoom,hover")    
r1 = plot2.line(x='x',y='y',source =source2, line_alpha = 1, line_width=1)
# safe data from plot 2 for later change in subroutine
ds1 = r1.data_source

def callback(attr, old, new):
    patch_name =  source1.data['colnames'][new['1d']['indices'][0]]
    ds1.data['y'] = df2[patch_name].values
    print("TapTool callback executed on Patch {}".format(patch_name))

# add taptool to plot1
barglyph.data_source.on_change('selected',callback)

curdoc().add_root(row(plot1,plot2))
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明


Ton*_*ony 5

这是独立文档的 JS 回调版本(在 Bokeh 1.0.4 上测试):

from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, TapTool
from bokeh.plotting import figure, show
import numpy as np

source_bars = ColumnDataSource({'x': [1, 2, 3], 'y': [2, 4, 1] , 'colnames': ['A', 'B', 'C']})
lines_y = [np.random.random(5) for i in range(3)]

plot1 = figure(tools = 'tap')
bars = plot1.vbar(x = 'x', top = 'y', source = source_bars, bottom = 0, width = 0.5)

plot2 = figure()
lines = plot2.line(x = 'x', y = 'y', source = ColumnDataSource({'x': np.arange(5), 'y': lines_y[0]}))
lines.visible = False

code = '''if (cb_data.source.selected.indices.length > 0){
            lines.visible = true;
            var selected_index = cb_data.source.selected.indices[0];
            lines.data_source.data['y'] = lines_y[selected_index]
            lines.data_source.change.emit(); 
          }'''

plots = row(plot1, plot2)
plot1.select(TapTool).callback = CustomJS(args = {'lines': lines, 'lines_y': lines_y}, code = code)
show(plots)
Run Code Online (Sandbox Code Playgroud)

结果:

在此处输入图片说明