Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bokeh Multi-Select widget callback not Working

I am new to Bokeh. Trying to implement few widget callbacks recently and hard to find any resources online.

Scenario: I have a multi_select bokeh widget with the list of the companies. Based on the companies selcted, data in the Vbar has to be changed. For this, i am trying to change the datasource that is used in the Vbar based on the values from Multi select. I am unable to get desired results. Can someone please help me out with this.

I am poor at CustomJs and hence doing this on Bokeh Server for callabcks. If there is a solution on CustomJs too, that would help me a lot.

Thank you very much for your time in Advance !

Below is the code I am using

source =source1[source1['year']== dt.now().year]
sourcex = source[source['month'] ==1 & 
source['CompanyNo'].isin(['01','02','03','04','05','08']) ]

Overall= ColumnDataSource(source)
Curr= ColumnDataSource(sourcex)

boolinit = source['month']==1
view = CDSView(source=Overall, filters=[BooleanFilter(boolinit)])

hover3 = HoverTool(
            tooltips = [
                ('day', '@day'),
                ('ExtendedPrice','@{ExtendedPrice}{0,0}'),
                ],
            formatters = {
                'day': 'datetime',
                'ExtendedPrice': 'numeral'}
           )

p =  figure(
    title='YEARLY SALES',  
    plot_width=600, 
    plot_height=400, 
    min_border=3,

tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],  
toolbar_location="above")
p.vbar(x='day', top='ExtendedPrice', width=0.2, color='#e8bc76', 
source=Curr)
p.xaxis.axis_label = 'Day'
p.xaxis.axis_label_text_font_style = 'normal'
p.xaxis.axis_label_text_font_size = '12pt'
p.yaxis[0].formatter = NumeralTickFormatter(format="0,0")



def Multi_Selectupdate(attrname, old, new):

    curr=sourcex[sourcex['CompanyNo'].isin(new)]
    source.data=curr.data




companies=['All']+sourcex['CompanyNo'].unique().tolist()
multi_select = MultiSelect(title="Select:", value=['01'], options=companies, 
height=200, width=100)

multi_select.on_change('value',Multi_Selectupdate )

layout = column(multi_select, p )

show(layout) 
like image 737
reddy Avatar asked Jan 28 '23 23:01

reddy


1 Answers

Since I don't have the data, I generated it using following script. I have assumed your data has 3 columns - Date, ExtendedPrice, CompanyNo. I have generated first a random data of 10K rows and then summarized it at CompanyNo, day, month, year level.

import pandas as pd
import random
CopmanyList = ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15']

df = pd.DataFrame({'base' : ["2017-01-01" for t in range(10000)],
    'Date' : [random.randint(0, 1035) for t in range(10000)], 
    'ExtendedPrice' : [random.random() for t in range(10000)],
    'CompanyNo' : [CopmanyList[random.randint(0, 15)] for t in range(10000)]})

df['base'] = pd.to_datetime(df['base'])
df["Date2"] = df.apply(lambda x: x["base"] + timedelta(days=x['Date']), axis=1)
df.drop(['base', 'Date'], axis=1, inplace=True)
df.set_index('Date2', inplace=True)
df['month'] = df.index.month
df['year'] = df.index.year
df['day'] = df.index.day
source1=df.groupby(['year','month','day', 'CompanyNo'], as_index = False)['ExtendedPrice'].sum()
source =source1[source1['year']== dt.now().year]
sourcex = source[source['month'] ==1]
sourcex = sourcex[sourcex['CompanyNo'].isin(['01'])]
sourcex.sort_values(by='day', inplace=True)

Now, what you want to accomplish is that given a day and set of company names, you need to publish a bar chart of some type of aggregation of ExtendedPrice, i.e. say there are 2 rows for company '01' and '02' (which are selected), and for month 2 (that is also selected by a slider) for current year, the ExtendedPrice value for these two is say 100, and 200. In case we want to show a sum, you need to show 300 in barchart. This summary, you need to calculate dynamically.

There are two ways to accomplish it.

1. Bokeh Callbacks

This will use native python functions, but you need to run it with bokeh serve , e.g bokeh serve --show <filename.py>. See the code below, a native python function compsel is filtering pandas dataframe and summarizing it and replacing the chart backend data with newly created data -

from datetime import timedelta
from datetime import datetime as dt
from bokeh.models.widgets import MultiSelect, Slider
from bokeh.layouts import widgetbox, column
from bokeh.models.ranges import FactorRange
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, HoverTool, CustomJS

Overall= ColumnDataSource(source)
Curr= ColumnDataSource(sourcex[['day', 'ExtendedPrice']])
Curr.remove('index')

hover3 = HoverTool(tooltips = [('day', '@day'),('Sales','@{ExtendedPrice}{0,0.00}')],
                   formatters = {'day': 'datetime','Sales': 'numeral'})

p =  figure(title='YEARLY SALES',  plot_width=600, plot_height=400, min_border=3,
tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],  
toolbar_location="above")

r = p.vbar(x='day', top='ExtendedPrice', width=0.2, color='#e8bc76', source=Curr)
p.xaxis.axis_label = 'Day'
p.xaxis.axis_label_text_font_style = 'normal'
p.xaxis.axis_label_text_font_size = '12pt'

def compsel(attr, old, new):
    vcomp = multi_select.value
    vmonth = slider.value
    sourcex = source[source['month'] ==vmonth]
    sourcex = sourcex[sourcex['CompanyNo'].isin(vcomp)]
    sourcex = sourcex.groupby(['day'], as_index = False)['ExtendedPrice'].sum()
    Currnew= ColumnDataSource(sourcex[['day', 'ExtendedPrice']])
    Currnew.remove('index')
    r.data_source.data=Currnew.data


companies=source['CompanyNo'].unique().tolist()
companies.sort()
multi_select = MultiSelect(title="Select:", value=['01'], options=companies, height=200, width=100)
slider = Slider(start=1, end=12, value=1, step=1, title="Month")
slider.on_change("value", compsel)
multi_select.on_change("value", compsel)

layout = column(multi_select, slider, p)

curdoc().add_root(layout)

There are other options to host this application. See other option by typing bokeh serve --help in terminal

2. JavaScript Callbacks

This will generate a javascript function to interact with the chart, which will not require bokeh server and work directly on the browser. This will require very simple code in javascript. In the example solved here, I am summarizing ExtendedPrice as sum, but code will require small tweaks to calculate other stats e.g. mean. The js callback function here does the similar thing as bokeh callback function, it reads the larger datasets and filters it based on the select and slider values -

source =source1[source1['year']== dt.now().year]
sourcex = source[source['month'] ==1]
sourcex = sourcex[sourcex['CompanyNo'].isin(['01'])]
sourcex.sort_values(by='day', inplace=True)

Overall= ColumnDataSource(source)
Curr= ColumnDataSource(sourcex[['day', 'ExtendedPrice']])
Curr.remove('index')

hover3 = HoverTool(tooltips = [('day', '@day'),('Sales','@{ExtendedPrice}{0,0.00}')],
                   formatters = {'day': 'datetime','Sales': 'numeral'})

p =  figure(title='YEARLY SALES',  plot_width=600, plot_height=400, min_border=3,
tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],  
toolbar_location="above")

r = p.vbar(x='day', top='ExtendedPrice', width=0.2, color='#e8bc76', source=Curr)
p.xaxis.axis_label = 'Day'
p.xaxis.axis_label_text_font_style = 'normal'
p.xaxis.axis_label_text_font_size = '12pt'

callms = CustomJS(args=dict(source=Overall, sc=Curr), code="""  
        var comp=msel.attributes.value;
        var f = slider.value;
        sc.data['ExtendedPrice'] = [];
        sc.data['day'] = [];
        for (var i = 0; i <= source.get_length(); i++){
          if (source.data['month'][i] == f){
            if (comp.indexOf(source.data['CompanyNo'][i]) >=0){
              var d1 = source.data['day'][i]
              if(typeof sc.data['day'][d1-1]=="undefined"){
                sc.data['day'][d1-1] = d1
                sc.data['ExtendedPrice'][d1-1] = source.data['ExtendedPrice'][i]
              }
              else{
                sc.data['ExtendedPrice'][d1-1] = sc.data['ExtendedPrice'][d1-1] + source.data['ExtendedPrice'][i]  
              }
            }
          }
        }
        sc.change.emit();
    """)

companies=source['CompanyNo'].unique().tolist()
companies.sort()
multi_select = MultiSelect(title="Select:", value=['01'], options=companies, height=200, width=100, callback=callms)
slider = Slider(start=1, end=12, value=1, step=1, title="Month", callback=callms)
callms.args["msel"] = multi_select
callms.args["slider"] = slider

layout = column(multi_select, slider, p)

output_file("Filterdata.html")
show(layout)
like image 178
Aritesh Avatar answered Jan 30 '23 14:01

Aritesh