在Python的Bokeh中使用JavaScript回调过滤数据

在Python的Bokeh中使用JavaScript回调过滤数据

本文介绍了在Python的Bokeh中使用JavaScript回调过滤数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于不准确/不理解的措词,我们事先表示歉意,因为这是我在这里的第一个问题.请随时指出我将来如何改进它.

我一直在阅读Bokeh的所有用户指南和各种论坛,但是由于这个问题一遍又一遍地出现而又没有一个可以普遍应用的答案,因此我认为这个问题仍然没有得到足够的解决.

我的任务是在Python的Bokeh中构建一个散点图,该散点图可以基于分类变量进行交互过滤.我对Javascript(以及数据的结构)的了解有限,这使我无法自己弄清这一点.

我发现,一种解决方案是附加满足条件的x& y值(例如使用JavaScript过滤Bokeh LabelSet ).但是,我也想保留所有其他变量,因为我使用它们来定义图形中的图形参数/悬停信息.

因此,我的问题是,如果其中一列满足Javascript中的特定条件,如何将整行附加到新的输出数据中?我也不确定我是否正确地调用了回调函数,以使绘图实际上对我的选择作出反应.因此,请随意在这里指出任何错误.

在此处查看一些示例代码:

  #Packages将熊猫作为pd导入将numpy导入为np从bokeh.plotting导入图中,output_file,显示将bokeh.events导入为bev将bokeh.models导入为bmo将bokeh.layouts导入为bla#数据数据= pd.DataFrame(数据= np.array([[1,1,'a',0.5],[2,2,'a',0.5],[3,3,'a',0.75],[4,4,'b',1],[5,5,'b',2]]),栏= ['x','y','类别','其他信息'])#设置output_file('dashboard.html')源= bmo.ColumnDataSource(数据)#定义下拉选项dropdown_options = [(''All','item_1'),None] + [(cat,str('item_'+ str(i)))for i,cat in enumerate(sorted(data ['category'].unique()),2)]#Generate下拉小部件dropdown = bmo.Dropdown(label ='Category',button_type ='default',menu = dropdown_options)#打回来callback = bmo.CustomJS(args = dict(source = source),代码="var data = source.data;var cat = cb_obj.value;如果(cat ='All'){数据= source.data} 别的 {var new_data = [];for(cat i = 0; i< = source.data ['category'].length; i ++){如果(source.data ['category'] [i] == cat){new_data.push(source.data [] [i])}}数据= new_data.data}source.data =数据source.change.emit();(")#链接动作dropdown.js_on_event(bev.MenuItemClick,回调)#阴谋p =图形(plot_width = 800,plot_height = 530,标题= None)p.scatter(x ='x',y ='y',source =源)显示(bla.column(dropdown,p)) 

不出所料,该过滤器不起作用.如前所述,由于我不知道如何在Javascript中索引整个行以及其他我做错的事情,因此非常感谢您提供帮助.

最诚挚的问候,奥利弗(Oliver)

解决方案

我为您的问题写了一个解决方案.我不是Bokeh专家,所以我可能不了解所有情况,但希望可以帮助您了解发生的情况.一些解释:

  • 开始时会有一些语法错误:在使用 cat i 的for循环中,您的意思可能是 var i

  • 如果您要将 All 分配给 cat ,则需要进行比较:使用 cat =='All' cat ==='All'

  • 您的 cb_obj.value 由于某些原因无法正常工作,并且返回未定义.您可以使用简单的 console.log(variableName)检查变量,然后在浏览器中打开开发人员控制台以查看回调的作用.我将您的列表理解方式更改为具有相同值的元组,而不是(category_name,item_category_number).现在, cb_obj.item 返回 category_name ,您可以与之进行比较.

  • 您应该了解数据的格式,例如,可以使用 console.log(source.data)来实现. source.data 是数组的对象(如果要用Python进行描述,则为列表的字典).因此,您无法像在for循环中那样推送数据,并且还遇到语法错误: source.data [] [i] -空时将无法访问所需内容括号.我编写了两个函数来处理此功能. generateNewDataObject 创建空数组的对象,我们可以使用 addRowToAccumulator

    附加该对象
  • 最后一件事是,我需要两个data_sources.首先,我们不会对其进行更改,其次,我们将对其进行修改并用于显示在绘图上.如果我们要修改第一个过滤器,那么在第一个过滤器之后,所有其他类别都将被删除,只有刷新页面才能使它们归还.不变"的data_source允许我们引用它,而不会在此过程中丢失经过过滤的数据.

我希望能帮上忙.

 #软件包将bokeh.events导入为bev将bokeh.layouts导入为bla将bokeh.models导入为bmo将numpy导入为np将熊猫作为pd导入从bokeh.plotting导入图中,output_file,显示# 数据数据= pd.DataFrame(数据= np.array([[1,1,'a',0.5],[2,2,'a',0.5],[3,3,'a',0.75],[4,4,'b',1],[5,5,'b',2]]),列= ['x','y','类别','其他信息'])# 设置output_file('dashboard.html')源= bmo.ColumnDataSource(数据)#定义下拉选项dropdown_options = [(全部",全部"),无] + [(猫,猫)为我,枚举的猫(sorted(data ['category'].unique()),2)]#生成下拉窗口小部件dropdown = bmo.Dropdown(label ='Category',button_type ='default',menu = dropdown_options)过滤后的数据= bmo.ColumnDataSource(数据)# 打回来回调= bmo.CustomJS(args = dict(unfiltered_data =来源,filtered_data = filtered_data),code ="var data = unfiltered_data.data;var cat = cb_obj.item;函数generateNewDataObject(oldDataObject){var newDataObject = {}用于(Object.keys(oldDataObject))的var键{newDataObject [key] = [];}返回newDataObject}函数addRowToAccumulator(累加器,dataObject,索引){用于(Object.keys(dataObject)的var键){accumulator [key] [index] = dataObject [key] [index];}返回累加器}如果(cat ==='All'){数据= unfiltered_data.data;} 别的 {var new_data = generateNewDataObject(data);for(var i = 0; i< = unfiltered_data.data ['category'].length; i ++){如果(unfiltered_data.data ['category'] [i] ==猫){new_data = addRowToAccumulator(new_data,unfiltered_data.data,i);}}数据= new_data;}filtered_data.data =数据;filtered_data.change.emit();")#链接动作dropdown.js_on_event(bev.MenuItemClick,回调)# 阴谋p1 =图形(plot_width = 800,plot_height = 530,title =无)p1.scatter(x ='x',y ='y',source = filtered_data)显示(bla.column(dropdown,p1)) 

apologies in advance for unprecise/unappreciated wording as this is my first question here.Feel free to point out how I can improve it in the future.

I have been reading through all of Bokeh's user guide and various forums but belief this question is still insufficiently covered as it appears over and over again without an answer that can be applied generically.

My task is to construct a scatterplot in Python's Bokeh that can interactively be filtered based on a categorical variable. My limited understanding of Javascript (and how the data is structured) prevents me from figuring this out by myself.

I found, that one solution is to append x&y values that fulfill the condition (f.e. Filtering Bokeh LabelSet with Javascript). However, I want to keep all the other variables as well, since I use them to define graphic parameters / hover information in the plot.

Therefore my question, how can I append whole rows to the new output data if one of the columns fulfills a certain condition in Javascript? I am also unsure if I call the callback correctly such that the plot would actually react to my selection. So please fell free to point out any mistakes here as well.

See some example code here:

#Packages
import pandas as pd
import numpy as np
from bokeh.plotting import figure, output_file, show
import bokeh.events as bev
import bokeh.models as bmo
import bokeh.layouts as bla

#Data
data = pd.DataFrame(data = np.array([[1,1,'a',0.5],
                                     [2,2,'a',0.5],
                                     [3,3,'a',0.75],
                                     [4,4,'b',1],
                                     [5,5,'b',2]]),
                    columns = ['x', 'y', 'category', 'other information'])


#Setup
output_file('dashboard.html')

source = bmo.ColumnDataSource(data)

#Define dropdown options
dropdown_options = [('All', 'item_1'), None] + [(cat, str('item_' + str(i))) for i, cat in enumerate(sorted(data['category'].unique()), 2)]

#Generate dropdown widget
dropdown = bmo.Dropdown(label = 'Category', button_type = 'default', menu = dropdown_options)


#Callback
callback = bmo.CustomJS(args = dict(source = source),
                        code = """

                        var data = source.data;

                        var cat = cb_obj.value;

                        if (cat = 'All'){

                            data = source.data

                        } else {

                            var new_data = [];

                            for (cat i = 0; i <= source.data['category'].length; i++){

                                    if (source.data['category'][i] == cat) {

                                            new_data.push(source.data[][i])

                                            }

                                    }

                            data = new_data.data

                        }

                        source.data = data

                        source.change.emit();

                        """)


#Link actions
dropdown.js_on_event(bev.MenuItemClick, callback)

#Plot
p = figure(plot_width = 800, plot_height = 530, title = None)

p.scatter(x = 'x', y = 'y', source = source)


show(bla.column(dropdown, p))

Unsurprisingly, the filter does not work. As said, any help highly appreciated since I do not know how to index whole rows in Javascript and whatever else I am doing wrong.

Best regards,Oliver

解决方案

I wrote a solution for your issue. I am no Bokeh expert so I might not know everything but hope that helps to understand what is going on. Some explanation:

  • You had some syntax errors to start with: at your for loop you used cat i, you probably meant var i

  • In your if you were assigning All to cat, you need to do the comparison: with either cat == 'All' or cat === 'All'

  • your cb_obj.value did not work for some reason and was returning undefined. You can check your variables with simple console.log(variableName) and open dev console in the browser to see callbacks in action. I changed your list comprehension to be tuple of the same values instead of (category_name, item_category_number). Now cb_obj.item returns category_name which you can do comparison with.

  • You should understand what format your data is in, you can do so with console.log(source.data) for example. source.data here is object of arrays (or dictionary of lists if you were to describe that in Python). Because of that you could not push the data the way you did in for loop and also you had a syntax error: source.data[][i] - you won't access what you want with empty bracket. I wrote two functions to handle this functionality. generateNewDataObject creates object of empty arrays that we can append with addRowToAccumulator

  • The last thing is that I needed were two data_sources. First that we will not do changes on and second that we will modify and use to display on the plot. If we were to modify the first one then after the first filter all other categories would be dropped and we could get them back only by refreshing the page. The 'immutable' data_source allows us to reference it and not lose filtered data in the process.

I hope that helps.

# Packages

import bokeh.events as bev
import bokeh.layouts as bla
import bokeh.models as bmo
import numpy as np
import pandas as pd
from bokeh.plotting import figure, output_file, show

# Data
data = pd.DataFrame(
    data=np.array(
        [
            [1, 1, 'a', 0.5],
            [2, 2, 'a', 0.5],
            [3, 3, 'a', 0.75],
            [4, 4, 'b', 1],
            [5, 5, 'b', 2]
        ]
    ),
    columns=['x', 'y', 'category', 'other information']
)

# Setup
output_file('dashboard.html')

source = bmo.ColumnDataSource(data)

# Define dropdown options
dropdown_options = [
                       ('All', 'All'), None
                   ] + [(cat, cat)
                       for i, cat in enumerate(sorted(data['category'].unique()), 2)
                   ]
# Generate dropdown widget
dropdown = bmo.Dropdown(label='Category', button_type='default', menu=dropdown_options)

filtered_data = bmo.ColumnDataSource(data)
# Callback
callback = bmo.CustomJS(
    args=dict(unfiltered_data=source, filtered_data=filtered_data),
    code="""

var data = unfiltered_data.data;
var cat = cb_obj.item;

function generateNewDataObject(oldDataObject){
    var newDataObject = {}
    for (var key of Object.keys(oldDataObject)){
        newDataObject[key] = [];
    }
    return newDataObject

}

function addRowToAccumulator(accumulator, dataObject, index) {
    for (var key of Object.keys(dataObject)){
        accumulator[key][index] = dataObject[key][index];
    }
    return accumulator;
}

if (cat === 'All'){
    data = unfiltered_data.data;
} else {
    var new_data =  generateNewDataObject(data);
    for (var i = 0; i <= unfiltered_data.data['category'].length; i++){
        if (unfiltered_data.data['category'][i] == cat) {
            new_data = addRowToAccumulator(new_data, unfiltered_data.data, i);
        }
    }
    data = new_data;
}

filtered_data.data = data;
filtered_data.change.emit();
"""
)

# Link actions
dropdown.js_on_event(bev.MenuItemClick, callback)

# Plot
p1 = figure(plot_width=800, plot_height=530, title=None)

p1.scatter(x='x', y='y', source=filtered_data)

show(bla.column(dropdown, p1))

这篇关于在Python的Bokeh中使用JavaScript回调过滤数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-29 04:49