Python/Bokeh - 如何通过使用 Select、回调和 CustomJS/js_on_change 从字典中按列值过滤行来更改数据源
Python/Bokeh - how the change data source by filtering rows by column value from dict with Select, callback and CustomJS/js_on_change
问题应该出在回调函数上。不幸的是,我没有 JS 经验。我从 dataframe-js 库中获取了这一部分,但它不起作用。这个想法是有一个仪表板,其中包含两个 Rate1 和 Rate2 的图表以及两个 Rates 类别的下拉菜单。
import pandas as pd
from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.plotting import figure, output_file
from bokeh.layouts import gridplot
from bokeh.io import show
d = {'Category': ['Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3'],
'Rate1': [1, 4, 3, 3, 7, 4, 9, 2, 6], 'Rate2': [3, 4, 6, 1, 9, 6, 8, 2, 1],
'Date': ['2021-06-21', '2021-06-21', '2021-06-21', '2021-06-22', '2021-06-22', '2021-06-22', '2021-06-23', '2021-06-23', '2021-06-23']}
df = pd.DataFrame(data=d)
output_file("with_dropdown_list.html")
category_default = "Cat1"
unique_categories = list(df.Category.unique())
source = ColumnDataSource(data={'y1': df.loc[df['Category'] == category_default].Rate1,
'y2': df.loc[df['Category'] == category_default].Rate2,
'date': df.loc[df['Category'] == category_default].Date})
output_file("time_series.html")
s1 = figure(title=category_default, x_axis_type="datetime", plot_width=500, plot_height=500)
s1.line(y='y1', x='date', source=source)
s2 = figure(title=category_default, x_axis_type="datetime", plot_width=500, plot_height=500)
s2.line(y='y2', x='date', source=source)
callback1 = CustomJS(args = {'source': source, 'data': data},
code = """source.data['y1'] = data['y1'].filter(row => row.get('Category') == cb_obj.value);""")
callback2 = CustomJS(args = {'source': source, 'data': data},
code = """source.data['y2'] = data['y2'].filter(row => row.get('Category') == cb_obj.value);""")
select1 = Select(title='Category Selection', value=category_default, options=unique_categories)
select1.js_on_change('value', callback1)
select2 = Select(title='Category Selection', value=category_default, options=unique_categories)
select2.js_on_change('value', callback2)
p = gridplot([[s1, s2], [select1, select2]])
show(p)
可能最快的方法是创建一个字典,将每个类别的值映射到适当的 Rate(rate1 或 rate2,具体取决于绘图)。您可以通过创建一个宽数据集来做到这一点,其中每一行代表一个唯一的日期:
df = pd.DataFrame(data=d).astype({"Date": "datetime64[ns]"}).pivot("Date", "Category", ["Rate1", "Rate2"])
print(df)
Rate1 Rate2
Category Cat1 Cat2 Cat3 Cat1 Cat2 Cat3
Date
2021-06-21 1 4 3 3 4 6
2021-06-22 3 7 4 1 9 6
2021-06-23 9 2 6 8 2 1
现在数据已经设置好了,我们可以轻松地使用 for 循环来创建每个图,而不是手动指定两者(完整代码):
import pandas as pd
from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.plotting import figure
from bokeh.layouts import gridplot
from bokeh.io import show
d = {
'Category': ['Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3'],
'Rate1': [1, 4, 3, 3, 7, 4, 9, 2, 6], 'Rate2': [3, 4, 6, 1, 9, 6, 8, 2, 1],
'Date': ['2021-06-21', '2021-06-21', '2021-06-21', '2021-06-22', '2021-06-22', '2021-06-22', '2021-06-23', '2021-06-23', '2021-06-23']
}
df = pd.DataFrame(data=d).astype({"Date": "datetime64[ns]"}).pivot("Date", "Category", ["Rate1", "Rate2"])
category_default = "Cat1"
unique_categories = list(df.columns.levels[1])
plot_figures = []
selectors = []
for y_name in ["Rate1", "Rate2"]:
subset = df[y_name] # select the appropriate "Rate" subset
subset_data = subset.to_dict("list") # dictionary-format: {'Cat1': [values...], 'Cat2': [values...], 'Cat3': [values...]}
source = ColumnDataSource({
"date": subset.index, # subset.index are the dates
"rate": subset_data[category_default]
})
p = figure(title=y_name, x_axis_type="datetime", plot_width=500, plot_height=500)
p.line(y="rate", x='date', source=source)
select = Select(title='Category Selection', value=category_default, options=unique_categories)
callback = CustomJS(
args={"subset_data": subset_data, "source": source},
code="""
source.data['rate'] = subset_data[cb_obj.value];
source.change.emit();
""")
select.js_on_change("value", callback)
plot_figures.append(p)
selectors.append(select)
p = gridplot([plot_figures, selectors])
show(p)
默认渲染图
正在将类别更新为 Cat2 和 Cat3
问题应该出在回调函数上。不幸的是,我没有 JS 经验。我从 dataframe-js 库中获取了这一部分,但它不起作用。这个想法是有一个仪表板,其中包含两个 Rate1 和 Rate2 的图表以及两个 Rates 类别的下拉菜单。
import pandas as pd
from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.plotting import figure, output_file
from bokeh.layouts import gridplot
from bokeh.io import show
d = {'Category': ['Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3'],
'Rate1': [1, 4, 3, 3, 7, 4, 9, 2, 6], 'Rate2': [3, 4, 6, 1, 9, 6, 8, 2, 1],
'Date': ['2021-06-21', '2021-06-21', '2021-06-21', '2021-06-22', '2021-06-22', '2021-06-22', '2021-06-23', '2021-06-23', '2021-06-23']}
df = pd.DataFrame(data=d)
output_file("with_dropdown_list.html")
category_default = "Cat1"
unique_categories = list(df.Category.unique())
source = ColumnDataSource(data={'y1': df.loc[df['Category'] == category_default].Rate1,
'y2': df.loc[df['Category'] == category_default].Rate2,
'date': df.loc[df['Category'] == category_default].Date})
output_file("time_series.html")
s1 = figure(title=category_default, x_axis_type="datetime", plot_width=500, plot_height=500)
s1.line(y='y1', x='date', source=source)
s2 = figure(title=category_default, x_axis_type="datetime", plot_width=500, plot_height=500)
s2.line(y='y2', x='date', source=source)
callback1 = CustomJS(args = {'source': source, 'data': data},
code = """source.data['y1'] = data['y1'].filter(row => row.get('Category') == cb_obj.value);""")
callback2 = CustomJS(args = {'source': source, 'data': data},
code = """source.data['y2'] = data['y2'].filter(row => row.get('Category') == cb_obj.value);""")
select1 = Select(title='Category Selection', value=category_default, options=unique_categories)
select1.js_on_change('value', callback1)
select2 = Select(title='Category Selection', value=category_default, options=unique_categories)
select2.js_on_change('value', callback2)
p = gridplot([[s1, s2], [select1, select2]])
show(p)
可能最快的方法是创建一个字典,将每个类别的值映射到适当的 Rate(rate1 或 rate2,具体取决于绘图)。您可以通过创建一个宽数据集来做到这一点,其中每一行代表一个唯一的日期:
df = pd.DataFrame(data=d).astype({"Date": "datetime64[ns]"}).pivot("Date", "Category", ["Rate1", "Rate2"])
print(df)
Rate1 Rate2
Category Cat1 Cat2 Cat3 Cat1 Cat2 Cat3
Date
2021-06-21 1 4 3 3 4 6
2021-06-22 3 7 4 1 9 6
2021-06-23 9 2 6 8 2 1
现在数据已经设置好了,我们可以轻松地使用 for 循环来创建每个图,而不是手动指定两者(完整代码):
import pandas as pd
from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.plotting import figure
from bokeh.layouts import gridplot
from bokeh.io import show
d = {
'Category': ['Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3', 'Cat1', 'Cat2', 'Cat3'],
'Rate1': [1, 4, 3, 3, 7, 4, 9, 2, 6], 'Rate2': [3, 4, 6, 1, 9, 6, 8, 2, 1],
'Date': ['2021-06-21', '2021-06-21', '2021-06-21', '2021-06-22', '2021-06-22', '2021-06-22', '2021-06-23', '2021-06-23', '2021-06-23']
}
df = pd.DataFrame(data=d).astype({"Date": "datetime64[ns]"}).pivot("Date", "Category", ["Rate1", "Rate2"])
category_default = "Cat1"
unique_categories = list(df.columns.levels[1])
plot_figures = []
selectors = []
for y_name in ["Rate1", "Rate2"]:
subset = df[y_name] # select the appropriate "Rate" subset
subset_data = subset.to_dict("list") # dictionary-format: {'Cat1': [values...], 'Cat2': [values...], 'Cat3': [values...]}
source = ColumnDataSource({
"date": subset.index, # subset.index are the dates
"rate": subset_data[category_default]
})
p = figure(title=y_name, x_axis_type="datetime", plot_width=500, plot_height=500)
p.line(y="rate", x='date', source=source)
select = Select(title='Category Selection', value=category_default, options=unique_categories)
callback = CustomJS(
args={"subset_data": subset_data, "source": source},
code="""
source.data['rate'] = subset_data[cb_obj.value];
source.change.emit();
""")
select.js_on_change("value", callback)
plot_figures.append(p)
selectors.append(select)
p = gridplot([plot_figures, selectors])
show(p)