Updating plot using Select Widget issue - python

I am currently trying to write a program that will switch between two sets of data when different options are chosen from the select widget. I am trying to make this program as autonomous as possible so in the future when people update the data they don't have to modify the code at all and the updates will happen automatically.
Currently, my issue is that when I select 'White' I want the plot to update but nothing is happening.
The two data sets are currently a dict of lists, one labeled 'White_dict' and the other labeled 'black_dict' solely to represent the color of the material for the data (I know its kinda ironic).
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, Legend
from bokeh.models import Select
from bokeh.layouts import column
import pandas as pd
from plot_tools import add_hover
import itertools
from collections import defaultdict
bokeh_doc = curdoc()
material_types = pd.read_csv('data/material-information.csv')
df = pd.read_csv('data/Black_Materials_total_reflecatance.csv')
black_df = pd.read_csv('data/Black_Materials_total_reflecatance.csv')
white_df = pd.read_csv('data/SPIE18_white_all.csv')
names = []
w_names = []
black_dict = defaultdict(list)
white_dict = defaultdict(list)
for name, w_name in zip(df, white_df):
names.append(name)
w_names.append(w_name)
data = pd.read_csv('data/Black_Materials_total_reflecatance.csv', usecols = names)
w_data = pd.read_csv('data/SPIE18_white_all.csv', usecols = w_names)
for name, w_name in zip(names, w_names):
for i in range(0, 2250):
black_dict[name].append(data[name][i])
white_dict[w_name].append(w_data[w_name][i])
mySource = ColumnDataSource(data = black_dict)
#create total reflectance figure
total_fig = figure(plot_width = 650, plot_height = 350,
title = 'Total Reflectance',
x_axis_label = 'Wavelength(nm)', y_axis_label = 'Total Reflectance',
x_range = (250, 2500), y_range = (0,10),
title_location = 'above', sizing_mode = "scale_both",
toolbar_location = "below",
tools = "box_zoom, pan, wheel_zoom, save")
select = Select(title="Material Type", options=['Black', 'White'])
def update_plot(attr, old, new):
if new == 'White':
mySource.data = white_dict
else:
mySource.data = black_dict
for name, color in zip(mySource.data, Turbo256):
if name != 'nm':
total_fig.line('nm', name, line_width = .7, source = mySource, color = color)
select.on_change('value', update_plot)
bokeh_doc.add_root(total_fig)
bokeh_doc.add_root(select)
I'm currently using bokeh serve bokehWork.py to launch the server. If anyone has any idea on what I should fix it would be much appreciated! Thanks!
EDIT:
Adding data for Black_materials_total_reflectance.csv
Black Reflectance Data sample
Adding data for White_all.csv
White Reflectance Data sample

There are two main issues with your code:
You read the same files multiple times and you do a lot of work that Pandas and Bokeh can already do for you
(the main one) You do not take into account the fact that different CSV files have different column names
Here's a fixed version. Notice also the usage of the palette. With just Turbo256 you were getting almost the same color for all lines.
import pandas as pd
from bokeh.models import ColumnDataSource, Select
from bokeh.palettes import turbo
from bokeh.plotting import figure, curdoc
black_ds = ColumnDataSource(pd.read_csv('/home/p-himik/Downloads/Black_material_data - Sheet1.csv').set_index('nm'))
white_ds = ColumnDataSource(pd.read_csv('/home/p-himik/Downloads/White Materials Sample - Sheet1.csv').set_index('nm'))
total_fig = figure(plot_width=650, plot_height=350,
title='Total Reflectance',
x_axis_label='Wavelength(nm)', y_axis_label='Total Reflectance',
title_location='above', sizing_mode="scale_both",
toolbar_location="below",
tools="box_zoom, pan, wheel_zoom, save")
total_fig.x_range.range_padding = 0
total_fig.x_range.only_visible = True
total_fig.y_range.only_visible = True
palette = turbo(len(black_ds.data) + len(white_ds.data))
def plot_lines(ds, color_offset, visible):
renderers = []
for name, color in zip(ds.data, palette[color_offset:]):
if name != 'nm':
r = total_fig.line('nm', name, line_width=.7, color=color,
source=ds, visible=visible)
renderers.append(r)
return renderers
black_renderers = plot_lines(black_ds, 0, True)
white_renderers = plot_lines(white_ds, len(black_ds.data), False)
select = Select(title="Material Type", options=['Black', 'White'], value='Black')
def update_plot(attr, old, new):
wv = new == 'White'
for r in white_renderers:
r.visible = wv
for r in black_renderers:
r.visible = not wv
select.on_change('value', update_plot)
bokeh_doc = curdoc()
bokeh_doc.add_root(total_fig)
bokeh_doc.add_root(select)

Related

How to update my Bokeh Legend to reflect Categorical Variable in Pandas Dataframe

I'm trying to make a dropdown menu with Bokeh that highlights the points in clusters I found. I have the dropdown menu working, but now I want to be able to visualize another categorical variable by color: Noun Class with levels of Masc, Fem, and Neuter. The problem is that the legend won't update when I switch which cluster I'm visualizing. Furthermore, if the first cluster I visualize doesn't have all 3 noun classes in it, the code starts treating all the other clusters I try to look at as (incorrectly) having that first cluster's noun class. For example, if Cluster 0 is the default and only has Masc points, all other clusters I look at using the dropdown menu are treated as only having Masc points even if they have Fem or Neuter in the actual DF.
My main question is this: how can I update the legend such that it's only attending to the respective noun classes of 'Curr'
Here's some reproducible code:
import pandas as pd
from bokeh.io import output_file, show, output_notebook, save, push_notebook
from bokeh.models import ColumnDataSource, Select, DateRangeSlider, CustomJS
from bokeh.plotting import figure, Figure, show
from bokeh.models import CustomJS
from bokeh.layouts import row,column,layout
import random
import numpy as np
from bokeh.transform import factor_cmap
from bokeh.palettes import Colorblind
import bokeh.io
from bokeh.resources import INLINE
#Generate reproducible DF
noun_class_names = ["Masc","Fem","Neuter"]
x = [random.randint(0,50) for i in range(100)]
y = [random.randint(0,50) for i in range(100)]
rand_clusters = [str(random.randint(0,10)) for i in range(100)]
noun_classes = [random.choice(noun_class_names) for i in range(100)]
df = pd.DataFrame({'x_coord':x, 'y_coord':y,'noun class':noun_classes,'cluster labels':rand_clusters})
df.loc[df['cluster labels'] == '0', 'noun class'] = 'Masc' #ensure that cluster 0 has all same noun class to illustrate error
clusters = [str(i) for i in range(len(df['cluster labels'].unique()))]
cols1 = df#[['cluster labels','x_coord', 'y_coord']]
cols2 = cols1[cols1['cluster labels'] == '0']
Overall = ColumnDataSource(data=cols1)
Curr = ColumnDataSource(data=cols2)
#plot and the menu is linked with each other by this callback function
callback = CustomJS(args=dict(source=Overall, sc=Curr), code="""
var f = cb_obj.value
sc.data['x_coord']=[]
sc.data['y_coord']=[]
for(var i = 0; i <= source.get_length(); i++){
if (source.data['cluster labels'][i] == f){
sc.data['x_coord'].push(source.data['x_coord'][i])
sc.data['y_coord'].push(source.data['y_coord'][i])
sc.data['noun class'].push(source.data['noun class'][i])
sc.data['cluster labels'].push(source.data['cluster labels'][i])
}
}
sc.change.emit();
""")
menu = Select(options=clusters, value='0', title = 'Cluster #') # create drop down menu
bokeh_p=figure(x_axis_label ='X Coord', y_axis_label = 'Y Coord', y_axis_type="linear",x_axis_type="linear") #creating figure object
mapper = factor_cmap(field_name = "noun class", palette = Colorblind[6], factors = df['noun class'].unique()) #color mapper for noun classes
bokeh_p.circle(x='x_coord', y='y_coord', color='gray', alpha = .5, source=Overall) #plot all other points in gray
bokeh_p.circle(x='x_coord', y='y_coord', color=mapper, line_width = 1, source=Curr, legend_group = 'noun class') # plotting the desired cluster using glyph circle and colormapper
bokeh_p.legend.title = "Noun Classes"
menu.js_on_change('value', callback) # calling the function on change of selection
bokeh.io.output_notebook(INLINE)
show(layout(menu,bokeh_p), notebook_handle=True)
Thanks in advance and I hope you have a nice day :)
Imma keep it real with y'all... The code works how I want now and I'm not entirely sure what I did. What I think I did was reset the noun classes in the Curr data source and then update the legend label field after selecting a new cluster to visualize and updating the xy coords. If anyone can confirm or correct me for posterity's sake I would appreciate it :)
Best!
import pandas as pd
import random
import numpy as np
from bokeh.plotting import figure, Figure, show
from bokeh.io import output_notebook, push_notebook, show, output_file, save
from bokeh.transform import factor_cmap
from bokeh.palettes import Colorblind
from bokeh.layouts import layout, gridplot, column, row
from bokeh.models import ColumnDataSource, Slider, CustomJS, Select, DateRangeSlider, Legend, LegendItem
import bokeh.io
from bokeh.resources import INLINE
#Generate reproducible DF
noun_class_names = ["Masc","Fem","Neuter"]
x = [random.randint(0,50) for i in range(100)]
y = [random.randint(0,50) for i in range(100)]
rand_clusters = [str(random.randint(0,10)) for i in range(100)]
noun_classes = [random.choice(noun_class_names) for i in range(100)]
df = pd.DataFrame({'x_coord':x, 'y_coord':y,'noun class':noun_classes,'cluster labels':rand_clusters})
df.loc[df['cluster labels'] == '0', 'noun class'] = 'Masc' #ensure that cluster 0 has all same noun class to illustrate error
clusters = [str(i) for i in range(len(df['cluster labels'].unique()))]
cols1 = df#[['cluster labels','x_coord', 'y_coord']]
cols2 = cols1[cols1['cluster labels'] == '0']
Overall = ColumnDataSource(data=cols1)
Curr = ColumnDataSource(data=cols2)
#plot and the menu is linked with each other by this callback function
callback = CustomJS(args=dict(source=Overall, sc=Curr), code="""
var f = cb_obj.value
sc.data['x_coord']=[]
sc.data['y_coord']=[]
sc.data['noun class'] =[]
for(var i = 0; i <= source.get_length(); i++){
if (source.data['cluster labels'][i] == f){
sc.data['x_coord'].push(source.data['x_coord'][i])
sc.data['y_coord'].push(source.data['y_coord'][i])
sc.data['noun class'].push(source.data['noun class'][i])
sc.data['cluster labels'].push(source.data['cluster labels'][i])
}
}
sc.change.emit();
bokeh_p.legend.label.field = sc.data['noun class'];
""")
menu = Select(options=clusters, value='0', title = 'Cluster #') # create drop down menu
bokeh_p=figure(x_axis_label ='X Coord', y_axis_label = 'Y Coord', y_axis_type="linear",x_axis_type="linear") #creating figure object
mapper = factor_cmap(field_name = "noun class", palette = Colorblind[6], factors = df['noun class'].unique()) #color mapper- sorry this was a thing that carried over from og code (fixed now)
bokeh_p.circle(x='x_coord', y='y_coord', color='gray', alpha = .05, source=Overall)
bokeh_p.circle(x = 'x_coord', y = 'y_coord', fill_color = mapper, line_color = mapper, source = Curr, legend_field = 'noun class')
bokeh_p.legend.title = "Noun Classes"
menu.js_on_change('value', callback) # calling the function on change of selection
bokeh.io.output_notebook(INLINE)
show(layout(menu,bokeh_p), notebook_handle=True)

local variable df referenced before assignment (Bokeh Interactive Visualization)

I just need a dropdown menu that shows all the departments and when we update the department it should generate the bar graph that gives all the products and it's corresponding retail value.
But it's giving a box with no visualization and a dropdown list.
Error: "local variable 'df' referenced before assignment"
import numpy as np
import pandas as pd
import os
import math
from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import ColumnDataSource, HoverTool, CustomJS
from bokeh.layouts import row, column
from bokeh.models.widgets import Dropdown
from bokeh.io import curdoc
df = pd.read_csv('/Users/austinejose/Desktop/My Files/Work/Newcastle Service Station/2018/Feb_Totals.csv')
source = ColumnDataSource(data=df)
names = [str(x) for x in df['Product Name']]
plot = figure(x_range = 'Product Name', plot_height = 500, width = 700, title = "Sales By Departments",
toolbar_location = "below")
plot.vbar(x = "Product Name", top = "Retail Value", width = 0.9, source = source, color = 'deepskyblue')
plot.xgrid.grid_line_color = "white"
plot.y_range.start = 0
plot.xaxis.major_label_orientation = math.pi/4
plot.xaxis.axis_label = "Product"
plot.yaxis.axis_label = "Retail Value"
plot.axis.minor_tick_in = -3
plot.axis.minor_tick_out = 6
plot.outline_line_color = "black"
menu = [("Bill Pay", "Bill Pay"), ("Hot Food", "Hot Food")]
menu_select = Dropdown(label='Department' ,menu=menu)
def dropdown_click(attr, old, new):
active_dropdown = menu_select.value
if active_dropdown in df['Department']:
df = df[df['Department']==active_dropdown]
source.data = df
menu_select.on_change('value', dropdown_click)
layout = row(menu_select, plot)
curdoc().add_root(layout)
You have a logic error in your callback:
def dropdown_click(attr, old, new):
active_dropdown = menu_select.value
if active_dropdown in df['Department']:
df = df[df['Department']==active_dropdown]
source.data = df
If active_dropdown in df['Department'] is False, then your code never defines df before trying to set source.data = df. This is the immediate cause of the message Error: "local variable 'df' referenced before assignment".
One possible solution is to move the assignment inside the if block, so that the assignment only ever happens when df is guaranteed to exist. Otherwise, you need to figure out what you want to heppen when the condition is False.
However, there is also a Bokeh usage error you will need to fix as well. The value of source.data has to be a plain Python dict, not a Pandas DataFrame.

Chart on click selection from data table in Bokeh

I've taken the below code from another source - it is not my own code.
The code allows you to select a cell in the data table, and the 'downloads' data for that cell will chart based on the row of the cell selected.
How do I expand this code such that if I have multiple variables (eg. 'downloads' and 'uploads') and so more columns in the data table, I can chart data based on that cell (so where row AND column are important)? Alternatively, how can I define as a variable the column number of a selected cell (in the same way selected_row below can be used to define the row number)?
from datetime import date
from random import randint
from bokeh.models import ColumnDataSource, Column
from bokeh.plotting import figure, curdoc
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn, Div
import numpy as np
data = dict(dates = [date(2014, 3, i + 1) for i in range(10)],
downloads = [randint(0, 100) for i in range(10)])
d_source = ColumnDataSource(data)
columns = [TableColumn(field = "dates", title = "Date", formatter = DateFormatter()),
TableColumn(field = "downloads", title = "Downloads")]
data_table = DataTable(source = d_source, columns = columns, width = 400, height = 280)
def table_select_callback(attr, old, new):
selected_row = new[0]
download_count = data['downloads'][selected_row]
chart_data = np.random.uniform(0, 100, size = download_count)
p = figure(title = 'bla')
r = p.line(x = range(len(chart_data)), y = chart_data)
root_layout.children[1] = p
d_source.selected.on_change('indices', table_select_callback)
root_layout = Column(data_table, Div(text = 'Select Date'))
curdoc().add_root(root_layout)
This is the final working code (run from command line with bokeh serve --show app.py):
from datetime import date
from random import randint
from bokeh.models import ColumnDataSource, Column, TableColumn, DateFormatter, DataTable, CustomJS
from bokeh.plotting import figure, curdoc
source = ColumnDataSource(dict(dates = [date(2014, 3, i + 1) for i in range(10)], downloads = [randint(0, 100) for i in range(10)]))
columns = [TableColumn(field = "dates", title = "Date", formatter = DateFormatter()), TableColumn(field = "downloads", title = "Downloads")]
data_table = DataTable(source = source, columns = columns, width = 400, height = 280, editable = True, reorderable = False)
info_source = ColumnDataSource(dict(row = [], column = []))
source_code = """
var grid = document.getElementsByClassName('grid-canvas')[0].children;
var row, column = '';
for (var i = 0,max = grid.length; i < max; i++){
if (grid[i].outerHTML.includes('active')){
row = i;
for (var j = 0, jmax = grid[i].children.length; j < jmax; j++)
if(grid[i].children[j].outerHTML.includes('active')) {
column = j;
source2.data = {row: [row], column: [column]};
}
}
}"""
callback = CustomJS(args = dict(source = source, source2 = info_source), code = source_code)
source.selected.js_on_change('indices', callback)
def py_callback(attr, old, new):
source.selected.update(indices = [])
print(info_source.data)
source.selected.on_change('indices', py_callback)
curdoc().add_root(Column(data_table))
My suggestion is to use my another post that uses a JS callback to access the row and column of the selected cell. This must be a JS callback as it uses HTML elements to walk through. So the steps are as follows:
Define a new ColumnDataSource that will contain the row and column number of a clicked cell
info_source = ColumnDataSource(dict(row = [], column = []))
Use the JS callback from this post to fill in the row and column values in this new table_info_source like this:
callback=CustomJS(args=dict(source=d_source,source2=info_source),code=source_code)
source.selected.js_on_change('indices', callback)
Inside the JS callback store the row and column index like this:
source2.data = {row:[row],column:[column]};
Access the info_source.data information in your Python callback to draw a plot
print (info_source.data)
Both callback are attached to the same source but usually JS callback is executed first so the data should be available in the Python callback on time. Having the index of row and column of the clicked cell you should be able to retrieve the required data and draw your chart.

Python bokeh slider not refreshing plot

I am creating a bokeh plot with a slider to refresh plot accordingly. There are 2 issues with the code posted.
1. The plot is not refreshed as per the slider. Please help in providing a fix for this issue.
2. Plot is not displayed with curdoc() when bokeh serve --show fn.ipynb is used
I'm trying to visualise this CSV file.
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, CategoricalColorMapper, HoverTool, Slider
from bokeh.plotting import figure, curdoc
from bokeh.palettes import viridis
from bokeh.layouts import row, widgetbox
#Importing and processing data file
crop = pd.read_csv('crop_production.csv')
#Cleaning Data
crop.fillna(np.NaN)
crop['Season'] = crop.Season.str.strip()
#Removing Whitespace #Filtering the dataset by Season
crop_season = crop[crop.Season == 'Whole Year']
crop_dt = crop_season.groupby(['State_Name', 'District_Name', 'Crop_Year']).mean().round(1)
#Creating Column Data Source
source = ColumnDataSource({
'x' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].Area,
'y' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].Production,
'state' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].index.get_level_values('State_Name'),
'district' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].index.get_level_values('District_Name')
})
#Creating color palette for plot
district_list = crop_dt.loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
# Creating the figure
#xmin, xmax = min(data.Crop_Year), max(data.Crop_Year)
#ymin, ymax = min(data.Production), max(data.Production)
p = figure(
title = 'Crop Area vs Production',
x_axis_label = 'Area',
y_axis_label = 'Production',
plot_height=900,
plot_width=1200,
tools = [HoverTool(tooltips='#district')]
)
p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district')
p.legend.location = 'top_right'
def update_plot(attr, old, new):
yr = slider.value
new_data = {
'x' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].Area,
'y' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].Production,
'state' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].index.get_level_values('State_Name'),
'district' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].index.get_level_values('District_Name')
}
source.data = new_data
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year')
slider.on_change('value',update_plot)
layout = row(widgetbox(slider), p)
curdoc().add_root(layout)
show(layout)
Also tried a different option using CustomJS as below, but still no luck.
callback = CustomJS(args=dict(source=source), code="""
var data = source.data;
var yr = slider.value;
var x = data['x']
var y = data['y']
'x' = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['ABC']), :].Area;
'y' = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['ABC']), :].Production;
p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district');
}
source.change.emit();
""")
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
yr_slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year', callback=callback)
callback.args["slider"] = yr_slider
Had a lot of issues trying to execute your code and I have changed some things, so feel free to correct me if did something wrong.
The error was caused by the creation of the ColumnDataSource, I had to change the level value to Crop_Year instead of Year. The loc 'ABC' also caused an error so I removed that too (And I had to add source = ColumnDataSource({, you probably forgot to copy that)
I also added a dropdown menu so it's possible to only show the data from one district.
Also, I'm not quite sure if it's possible to start a bokeh server by supplying a .ipynb file to --serve. But don't pin me down on that, I never use notebooks. I've tested this with a .py file.
#!/usr/bin/python3
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, CategoricalColorMapper, HoverTool
from bokeh.plotting import figure, curdoc
from bokeh.palettes import viridis
from bokeh.layouts import row, widgetbox
from bokeh.models.widgets import Select, Slider
#Importing and processing data file
crop = pd.read_csv('crop_production.csv')
#Cleaning Data
crop.fillna(np.NaN)
crop['Season'] = crop.Season.str.strip()
#Removing Whitespace #Filtering the dataset by Season
crop_season = crop[crop.Season == 'Whole Year']
crop_dt = crop_season.groupby(['State_Name', 'District_Name', 'Crop_Year']).mean().round(1)
crop_dt_year = crop_dt[crop_dt.index.get_level_values('Crop_Year')==2001]
crop_dt_year_state = crop_dt_year[crop_dt_year.index.get_level_values('State_Name')=='Tamil Nadu']
#Creating Column Data Source
source = ColumnDataSource({
'x': crop_dt_year_state.Area.tolist(),
'y': crop_dt_year_state.Production.tolist(),
'state': crop_dt_year_state.index.get_level_values('State_Name').tolist(),
'district': crop_dt_year_state.index.get_level_values('District_Name').tolist()
})
#Creating color palette for plot
district_list = crop_dt.loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
# Creating the figure
p = figure(
title = 'Crop Area vs Production',
x_axis_label = 'Area',
y_axis_label = 'Production',
plot_height=900,
plot_width=1200,
tools = [HoverTool(tooltips='#district')]
)
glyphs = p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district')
p.legend.location = 'top_right'
def update_plot(attr, old, new):
#Update glyph locations
yr = slider.value
state = select.value
crop_dt_year = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr]
crop_dt_year_state = crop_dt_year[crop_dt_year.index.get_level_values('State_Name')==state]
new_data = {
'x': crop_dt_year_state.Area.tolist(),
'y': crop_dt_year_state.Production.tolist(),
'state': crop_dt_year_state.index.get_level_values('State_Name').tolist(),
'district': crop_dt_year_state.index.get_level_values('District_Name').tolist()
}
source.data = new_data
#Update colors
district_list = crop_dt.loc[([state]), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
glyphs.glyph.fill_color = dict(field='district', transform=color_mapper)
glyphs.glyph.line_color = dict(field='district', transform=color_mapper)
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year')
slider.on_change('value',update_plot)
#Creating drop down for state
options = list(set(crop_dt.index.get_level_values('State_Name').tolist()))
options.sort()
select = Select(title="State:", value="Tamil Nadu", options=options)
select.on_change('value', update_plot)
layout = row(widgetbox(slider, select), p)
curdoc().add_root(layout)
#Jasper Thanks a lot. This works, however it doesnt work with .loc[(['Tamil Nadu']), :]. Reason for having this is to filter the data by adding a bokeh dropdown or radio button object and refresh the plot based on the filters. The below code works only if .loc[(['Tamil Nadu']), :] is removed. Is there any other way to fix this please?
def update_plot(attr, old, new):
yr = slider.value
new_data = {
'x' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].Area.tolist(),
'y' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].Production.tolist(),
'state' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].index.get_level_values('State_Name').tolist(),
'district' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').tolist()
}
source.data = new_data

AttributeError: 'DataFrame' object has no attribute 'add_root' bokeh

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel
from bokeh.models.widgets import CheckboxGroup, Slider, RangeSlider, Tabs
from bokeh.layouts import column, row, WidgetBox
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
output_notebook()
def histogram_tab(webs):
def make_dataset(params_list, range_start = 0.0, range_end = 1, bin_width = 0.005):
#check to make sure the start is less than the end
assert range_start < range_end, "Start must be less than end!"
#by_params = pd.DataFrame(columns=[ ,'Max', 'Avarage', 'Min','color'])
by_params = pd.DataFrame(columns=[ 'left','right', 'proportion', 'p_proportion','p_interval', 'name', 'w_name','color'])
#
range_extent = range_end - range_start
values = ['Min', "Avarage", 'Max']
# Iterate through all the parameters
for i, para_name in enumerate(params_list):
#print para_name
# Subset to the parameter
subset = webs[para_name]
# note: subset have to be a list of values
# [webs.columns[i%6]]
# Create a histogram with specified bins and range
arr_hist, edges = np.histogram(subset,
bins = int(range_extent / bin_width),
range = [range_start, range_end])
# Divide the counts by the total to get a proportion and create df
arr_df= pd.DataFrame({'proportion': arr_hist ,
'left': edges[:-1], 'right': edges[1:]}) #/ np.sum(arr_hist)
# Format the proportion
arr_df['p_proportion'] = ['%0.00005f' % proportion for proportion in arr_df['proportion']]
# Format the interval
arr_df['p_interval'] = ['%d to %d scale' % (left, right) for left,
right in zip(arr_df['left'], arr_df['right'])]
# Assign the parameter for labels
arr_df['name'] = para_name
arr_df['w_name'] = webs['Site name']
# Color each parametr differently
arr_df['color'] = Category20_16[i]
# Add to the overall dataframe
by_params = by_params.append(arr_df)
# Overall dataframe
by_params = by_params.sort_values(['name','left'])
return ColumnDataSource(by_params)
def style(p):
# Title
p.title.align = 'center'
p.title.text_font_size ='20pt'
p.title.text_font = 'serif'
# Axis titles
p.xaxis.axis_label_text_font_size = '14pt'
p.xaxis.axis_label_text_font_style = 'bold'
p.yaxis.axis_label_text_font_size = '14pt'
p.yaxis.axis_label_text_font_style = 'bold'
# Tick labels
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
return p
def make_plot(src):
# Blank plot with correct labels
p = figure(plot_width = 700, plot_height = 700,
title = "Histogram of Parametes for the websites",
x_axis_label = 'parameters', y_axis_label = "values")
# Quad glyphs to create a histogram
p.quad(source=src, bottom =0,left = 'left', right = 'right', color ='color', top= 'proportion',fill_alpha = 0.7, hover_fill_color = 'color', legend = 'name',
hover_fill_alpha = 1.0, line_color = 'white') #top='proportion',
# Hover tool with vline mode
hover = HoverTool(tooltips=[('Parameter','#name'),
('Website','#w_name'),
('Proportion','p_proportion')
],
mode='vline')
p.add_tools(hover)
# Stypling
p = style(p)
return p
# Update function takes three default parameters
def update(attr, old, new):
# Get the list of parameter for the graph
parameter_to_plot = [para_selection.labels[i] for i in para_selection.active]
# Make a new dataset based on the selected parameter and the
# make_dataset function defined earlier
new_src = make_dataset(parameter_to_plot, range_start = 0, range_end = 1, bin_width = 0.005) # note range are not specified
# Convert dataframe to column data source
new_src = ColumnDataSource(new_src)
# Update the source used the quad glpyhs
src.data.update(new_src.data)
list_of_params = list(webs.columns[1:].unique())
list_of_params.sort()
para_selection = CheckboxGroup(labels=list_of_params, active = [0,1])
para_selection.on_change('active',update)
binwidth_select = Slider(start =0, end = 1,
step = 0.00025, value = 0.0005,
title = 'Change in parameter')
binwidth_select.on_change('value', update)
range_select = RangeSlider(start=0, end=1, value =(0,1),
step=0.00025, title = 'Change in range')
range_select.on_change('value', update)
initial_params = [para_selection.labels[i] for i in para_selection.active]
src = make_dataset(initial_params,
range_start = range_select.value[0],
range_end = range_select.value[1],
bin_width = binwidth_select.value)
p = make_plot(src)
#show(p)
# Put controls in a single element
controls = WidgetBox(para_selection, binwidth_select, range_select)
# Create a row layout
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child = layout, title = 'Histogram')
#return tab
tabs = Tabs(tabs=[tab])
webs.add_root(tabs)
# Set up an application
handler = FunctionHandler(histogram_tab(webs))
app = Application(handler)
add_root is a method on Document, you are trying to call it on a DataFrame called webs, apparently, which is why you get that message. The structure of a Bokeh app in a notebook should look like this:
# create a function to define the app, must accept "doc" as the parameter
def myfunc(doc):
# make Bokeh objects
# add stuff to doc
doc.add_root(stuff)
# pass the function, but *don't* execute it
handler = FunctionHandler(myfunc)
app = Application(handler)
Note that the last two lines are not necessary in recent version of Bokeh, you can just call:
show(myfunc)
directly. There is a full example in the repo:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
Your code should be structured very similarly to that.

Categories

Resources