Bokeh linking/ brushing based on column instead of row indices / index

ε祈祈猫儿з 提交于 2019-12-08 05:06:38

问题


I have Pandas DataFrame that is similar to this:

   person activities  hours  foodeaten
0       1          a      3         12
1       1          b      4         14
2       1          c      6         34
3       2          a      2         45
4       2          b      7         67
5       3          a      5          5
6       3          b      3         -1
7       3          c      2          3
8       3          d     12          5

I would like to plot the hours spent by a person on a activity, per activity. so, plot 1: activity a, with x=persons y=hours for activity a plot 2: activity b, with x=persons y=hours for activity b etc.

I want to link the plots based on persons, not on index. So when I select the circle for person 1 in plot 1, this person's hours in the other plots should highlight, for my purpose as well.

MWE:

from bokeh.io import output_notebook, show, output_file, reset_output
from bokeh.plotting import figure
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter
import pandas as pd

# data
data = {'person':[1,1,1,2,2,3,3,3,3],'activities':['a','b','c','a','b','a','b','c','d'], 'hours':[3,4,6,2,7,5,3,2,12],
       'foodeaten':[12,14,34,45,67,5,-1,3,5]}
df = pd.DataFrame(data=data)
source = ColumnDataSource(data=df)

# filtering the data on activity
v1 = df.activities=='a'
v2 = df.activities=='b'
v3 = df.activities=='c'
v4 = df.activities=='d'

# creating a view to filter the source data on activity
view1 = CDSView(source=source, filters=[BooleanFilter(v1.values.tolist())])
view2 = CDSView(source=source, filters=[BooleanFilter(v2.values.tolist())])
view3 = CDSView(source=source, filters=[BooleanFilter(v3.values.tolist())])
view4 = CDSView(source=source, filters=[BooleanFilter(v4.values.tolist())])

# Plot options
tools       = 'pan,box_select,lasso_select,help,poly_select,hover,wheel_zoom,reset'
plot_width  = 300
plot_height = 300
TOOLTIPS    = [("Person", "@person"),
               ("hours",  "@hours"),]
plot_options = dict(plot_width=plot_width, plot_height=plot_height, tools =tools, tooltips= TOOLTIPS)

# plotting
p1 = figure(title ='activity a',**plot_options)
p1.circle('person', y='hours', size=15, view=view1, source=source)
plot_options['x_range']= p1.x_range

p2 = figure(title ='activity b',**plot_options)
p2.circle('person', y='hours', size=15, view=view2, source=source)

p3 = figure(title ='activity c',**plot_options)
p3.circle('person', y='hours', size=15, view=view3, source=source)

p4 = figure(title ='activity d',**plot_options)
p4.circle('person', y='hours', size=15, view=view4, source=source)

p12 = figure(title ='activity a',**plot_options)
p12.circle('person', y='foodeaten', size=15, view=view1, source=source)
plot_options['x_range']= p1.x_range

p22 = figure(title ='activity b',**plot_options)
p22.circle('person', y='foodeaten', size=15, view=view2, source=source)

p32 = figure(title ='activity c',**plot_options)
p32.circle('person', y='foodeaten', size=15, view=view3, source=source)

p42 = figure(title ='activity d',**plot_options)
p42.circle('person', y='foodeaten', size=15, view=view4, source=source)

p = gridplot([[p1,p12],[p2,p22],[p3,p32],[p4,p42]])
output_file('test.html')
show(p)

This example shows the linking behaviour, row wise, but I would like to highlight everything for a person by selecting an attribute of this person in a graph.


回答1:


Here is a working example for arbitrary number of plots:

from bokeh.plotting import figure, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJS
import pandas as pd

data = {'person': [1, 1, 1, 2, 2, 3, 3, 3, 3], 'activities':['a', 'b', 'c', 'a', 'b', 'a', 'b', 'c', 'd'], 'hours':[3, 4, 6, 2, 7, 5, 3, 2, 12], 'foodeaten':[12, 14, 34, 45, 67, 5, -1, 3, 5]}
df = pd.DataFrame(data = data)
source = ColumnDataSource(data = df)
views = [(df.activities == l) for l in ['a', 'b', 'c', 'd']]
filtered_views = [CDSView(source = source, filters = [BooleanFilter(view.values.tolist())]) for view in views]
plot_options = dict(plot_width = 250, plot_height = 250, tools = "tap,pan,wheel_zoom,reset,save", tooltips = [("Person", "@person"), ("hours", "@hours")])
plots = [figure(title = 'activity {l}'.format(l = l), **plot_options) for l in ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd']]
[plot.circle('person', y = name, size = 15, view = view, source = source) for plot, view, name in zip(plots, 2 * filtered_views, 4 * ['hours'] + 4 * ['foodeaten'])]
callback = CustomJS(args = dict(source = source, plots = plots), code = """
const selected_index = source.selected.indices[0]
const person = source.data['person'][selected_index]
var all_selected = [];
for (index in source.data['index']){
    if (source.data['person'][index] == person)
    all_selected.push(index) 
}
source.selected.indices = all_selected; """)

[plot.js_on_event('tap', callback) for plot in plots]
show(gridplot(children = [plot for plot in plots], ncols = 2))




回答2:


Bokeh does not have anything built in for this kind of automatic linking. However, it is possible to update the selection of one glyph based on the selection of another, using CustomJS callbacks:

from bokeh.io import show
from bokeh.layouts import row
from bokeh.models import CustomJS
from bokeh.plotting import figure

p1 = figure(plot_width=300, plot_height=300, tools="tap")
r1 = p1.circle(x=[1, 2], y=1, color=["red", "blue"], size=20)

p2 = figure(plot_width=300, plot_height=300, tools="")
r2 = p2.circle(x=[1, 1, 2], y=[1, 2, 1.5], color=["red", "red", "blue"], size=20)

callback = CustomJS(args=dict(s2=r2.data_source), code="""
const s2_inds = []
if (cb_obj.indices.indexOf(0) >= 0) {
  s2_inds.push(0)
  s2_inds.push(1)
}
if (cb_obj.indices.indexOf(1) >= 0) {
  s2_inds.push(2)
}
s2.selected.indices = s2_inds
"""))

show(row(p1, p2))

With this code, selecting the left red circle will select all the right red circles, and the same for the blue:

Note that the code above explicitly and manually hand-codes the relationship between the indices, in order to illustrate the general technique (i.e. it specifies a table by hand). You will probably want to use knowledge and assumptions about your specific data to craft a more general CustomJS callback that can compute which indices to set automatically.



来源:https://stackoverflow.com/questions/54984453/bokeh-linking-brushing-based-on-column-instead-of-row-indices-index

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!