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

2024-05-23 15:27:22 发布

您现在位置:Python中文网/ 问答频道 /正文

由于这是我在这里提出的第一个问题,我对不准确/不受欢迎的措辞提前表示歉意。 请随时指出我将来如何改进它

我一直在阅读Bokeh的所有用户指南和各种论坛,但我相信这个问题仍然没有被充分涵盖,因为它一遍又一遍地出现,没有一个可以普遍应用的答案

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

我发现,一种解决方案是附加x&;满足条件的y值(f.e.Filtering Bokeh LabelSet with Javascript)。但是,我也希望保留所有其他变量,因为我使用它们来定义绘图中的图形参数/悬停信息

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

请参见此处的一些示例代码:

#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))

毫不奇怪,过滤器不起作用。如前所述,非常感谢您的帮助,因为我不知道如何在Javascript中索引整行,也不知道我做错了什么

致以最良好的祝愿, 奥利弗


Tags: 数据importsourcenewdatavarasbokeh
1条回答
网友
1楼 · 发布于 2024-05-23 15:27:22

我为你的问题写了一个解决方案。我不是博克专家,所以我可能不知道一切,但希望这有助于了解正在发生的事情。一些解释:

  • 您首先遇到了一些语法错误:在for循环中,您使用了cat i,可能是指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追加它

  • 最后一件事是我需要两个数据源。首先,我们将不在上进行更改,其次,我们将修改并用于在绘图上显示。如果我们要修改第一个,那么在第一个过滤器之后,所有其他类别都将被删除,我们只能通过刷新页面才能将它们恢复。“不可变”数据源允许我们引用它,而不会在过程中丢失过滤后的数据

我希望这有帮助

# 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))

相关问题 更多 >