在Bokeh回调中切片数据以产生类似仪表板的交互

时间:2017-10-13 08:47:57

标签: python bokeh

我正在尝试使用回调函数为我的条形图生成类似交互的仪表板,而不使用bokeh serve功能。最终,如果更改了两个下拉菜单中的任何一个,我希望能够更改绘图。到目前为止,这只适用于阈值是硬编码的。我只知道如何提取cb_obj值而不是实际上没有调用的下拉列表。我查看了thisthis回答,以制定首次尝试。

这是我的代码:

from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column

import numpy as np
import pandas as pd


def generate_data(factor=10):
    rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
    idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
                               [20,999]],names=["Experiment","Threshold"])
    rawdata.index = idx

    return rawdata.reset_index()

# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0") 
                            & (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0") 
                            & (error_data.Threshold == 20)][["A","B","C","D"]].values[0]

# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)

# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts, 
                                    upper=initial_counts+initial_errors, 
                                    lower=initial_counts-initial_errors))

# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
       line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
                                                  factors=groups))

# Error bars 
p.add_layout(
    Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)

def callback(source=source, count_source = count_source, error_source=error_source, window=None):

    def slicer(data_source, experiment, threshold, dummy_col, columns):
        """ Helper function to enable lookup of data."""
        count = 0
        for row in data_source[dummy_col]:
            if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
                result = [data_source[col][count] for col in columns]

            count+=1

        return result

    # Initialise data sources
    data = source.data
    count_data = count_source.data
    error_data = error_source.data

    # Initialise values
    experiment = cb_obj.value
    threshold = 20
    counts, upper, lower = data["counts"], data["upper"], data["lower"]

    tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
    temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])

    # Select values and emit changes
    for i in range(len(counts)):
        counts[i] = tempdata[i]

    for i in range(len(counts)):
        upper[i] = counts[i]+temperror[i]
        lower[i] = counts[i]-temperror[i]

    source.change.emit()


exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))    
thr_dropdown = Select(title="Select:", value="12", options=list(count_data.Threshold.astype(str).unique()))    

exp_dropdown.callback = CustomJS.from_py_func(callback)


p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"

layout = column(exp_dropdown,thr_dropdown, p)

show(layout)

1 个答案:

答案 0 :(得分:1)

问题的解决方案是需要在回调函数之前定义Select菜单。此代码有效:

from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column

import numpy as np
import pandas as pd


def generate_data(factor=10):
    rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
    idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
                               [20,999]],names=["Experiment","Threshold"])
    rawdata.index = idx

    return rawdata.reset_index()

# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0") 
                            & (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0") 
                            & (error_data.Threshold == 20)][["A","B","C","D"]].values[0]

# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)

# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts, 
                                    upper=initial_counts+initial_errors, 
                                    lower=initial_counts-initial_errors))

# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
       line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
                                                  factors=groups))
# Error bars 
p.add_layout(
    Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)    

exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))    
thr_dropdown = Select(title="Select:", value="20", options=list(count_data.Threshold.astype(str).unique()))

def callback(source=source, count_source = count_source, error_source=error_source, exp_dropdown = exp_dropdown,
             thr_dropdown=thr_dropdown,window=None):

    def slicer(data_source, experiment, threshold, dummy_col, columns):
        """ Helper function to enable lookup of data."""
        count = 0
        for row in data_source[dummy_col]:
            if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
                result = [data_source[col][count] for col in columns]

            count+=1

        return result

    # Initialise data sources
    data = source.data
    count_data = count_source.data
    error_data = error_source.data

    # Initialise values
    experiment = exp_dropdown.value
    threshold = thr_dropdown.value
    counts, upper, lower = data["counts"], data["upper"], data["lower"]

    tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
    temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])

    # Select values and emit changes
    for i in range(len(counts)):
        counts[i] = tempdata[i]

    for i in range(len(counts)):
        upper[i] = counts[i]+temperror[i]
        lower[i] = counts[i]-temperror[i]

    source.change.emit()


exp_dropdown.callback = CustomJS.from_py_func(callback)
thr_dropdown.callback = CustomJS.from_py_func(callback)

p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"

layout = column(exp_dropdown,thr_dropdown, p)

show(layout)