在 Python 的 Bokeh 中使用 Javascript 回调过滤数据
Filter data with Javascript callback in Python's Bokeh
提前为 unprecise/unappreciated 措辞道歉,因为这是我在这里的第一个问题。
欢迎指出我以后如何改进它。
我已经通读了 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 中的整行以及我做错了什么。
此致,
奥利弗
我为你的问题写了一个解决方案。我不是散景专家,所以我可能不是什么都知道,但希望这有助于理解正在发生的事情。一些解释:
您开始时有一些语法错误:在您的 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
returns category_name
你可以与之进行比较。
您应该了解您的数据的格式,例如,您可以使用 console.log(source.data)
来了解。 source.data
这里是数组的对象(或者如果你要在 Python 中描述的话,就是列表的字典)。因此,您无法按照在 for 循环中所做的方式推送数据,并且出现语法错误:source.data[][i]
- 您将无法使用空括号访问您想要的内容。我写了两个函数来处理这个功能。 generateNewDataObject
创建我们可以附加 addRowToAccumulator
的空数组对象
最后一件事是我需要两个 data_sources。首先,我们不会进行更改,其次,我们将修改并用于显示在绘图上。如果我们要修改第一个,那么在第一个过滤器之后,所有其他类别都将被删除,我们只能通过刷新页面来恢复它们。 'immutable' data_source 允许我们引用它并且不会在此过程中丢失过滤后的数据。
希望对您有所帮助。
# 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))
提前为 unprecise/unappreciated 措辞道歉,因为这是我在这里的第一个问题。 欢迎指出我以后如何改进它。
我已经通读了 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 中的整行以及我做错了什么。
此致, 奥利弗
我为你的问题写了一个解决方案。我不是散景专家,所以我可能不是什么都知道,但希望这有助于理解正在发生的事情。一些解释:
您开始时有一些语法错误:在您的 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
returnscategory_name
你可以与之进行比较。您应该了解您的数据的格式,例如,您可以使用
的空数组对象console.log(source.data)
来了解。source.data
这里是数组的对象(或者如果你要在 Python 中描述的话,就是列表的字典)。因此,您无法按照在 for 循环中所做的方式推送数据,并且出现语法错误:source.data[][i]
- 您将无法使用空括号访问您想要的内容。我写了两个函数来处理这个功能。generateNewDataObject
创建我们可以附加addRowToAccumulator
最后一件事是我需要两个 data_sources。首先,我们不会进行更改,其次,我们将修改并用于显示在绘图上。如果我们要修改第一个,那么在第一个过滤器之后,所有其他类别都将被删除,我们只能通过刷新页面来恢复它们。 'immutable' data_source 允许我们引用它并且不会在此过程中丢失过滤后的数据。
希望对您有所帮助。
# 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))