I have a pandas dataframe I am pulling data from and showing as a bar plot using Bokeh. What I want is show the max value of each bar upon hover. This is the first day I'm using Bokeh and I already changed the code a couple times and I'm really confused how to set it up. I added the:
p.add_tools(HoverTool(tooltips=[("x_ax", "#x_ax"), ("y_ax", "#y_ax")]))
line, but just don't understand it.
Here's the code:
from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource, ranges, LabelSet
from bokeh.plotting import figure, save, gridplot, output_file
# prepare some data
# x = pd.Series(range(1,36))
x_ax = FAdf['SampleID']
y_ax = FAdf['First Run Au (ppm)']
# output to static HTML file
output_file("bars.html")
# create a new plot with a title and axis labels
p = figure(x_range=x_ax, title="Batch results", x_axis_label='sample', y_axis_label='Au (ppm)',
toolbar_location="above", plot_width=1200, plot_height=800)
p.add_tools(HoverTool(tooltips=[("x_ax", "#x_ax"), ("y_ax", "#y_ax")]))
# setup for the bars
p.vbar(x=x_ax, top=y_ax, width=0.9)
p.xgrid.grid_line_color = None
p.y_range.start = 0
# turn bar tick labels 45 deg
p.xaxis.major_label_orientation = np.pi/3.5
# show the results
show(p)
Sample from the FAdf database:
SampleID:
0 KR-19 349
1 KR-19 351
2 Blank_2
3 KR-19 353
First Run Au (ppm):
0 0.019
1 0.002
2 0.000
3 0.117
If you pass actual literal data sequences to a glyph method like you have above, then Bokeh uses generic field names like "x" and "y" since it has no way of knowing any other names use. These are the columns you would need to configure the hover tool with:
tooltips=[("x_ax", "#x"), ("y_ax", "#y")])
Alternatively, you can pass a source argument to the vbar method so that the columns have the column names that you prefer. This is described in the Users Guide:
https://docs.bokeh.org/en/latest/docs/user_guide/data.html
Related
I'm using the datetime axis of Bokeh. In the Bokeh data source, I have my x in numpy datetime format and others are y numbers. I'm looking for a way to show the label of the x datetimx axis right below the point. I want Bokeh to show the exact datetime that I provided via my data source, not some approximation! For instance, I provide 5:15:00 and it shows 5:00:00 somewhere before the related point.I plan to stream data to the chart every 1 hour, and I want to show 5 points each time. Therefore, I need 5 date-time labels. How can I do that? I tried p.yaxis[0].ticker.desired_num_ticks = 5 but it didn't help. Bokeh still shows as many number of ticks as it wants! Here is my code and result:
import numpy as np
from bokeh.models.sources import ColumnDataSource
from bokeh.plotting import figure
from bokeh.io import show
from bokeh.palettes import Category10
p = figure(x_axis_type="datetime", plot_width=800, plot_height=500)
data = {'x':
[np.datetime64('2019-01-26T03:15:10'),
np.datetime64('2019-01-26T04:15:10'),
np.datetime64('2019-01-26T05:15:10'),
np.datetime64('2019-01-26T06:15:10'),
np.datetime64('2019-01-26T07:15:10')],
'A': [10,25,15,55,40],
'B': [60,50,80,65,120],}
source = ColumnDataSource(data=data)
cl = Category10[3][1:]
r11 = p.line(source=source, x='x', y='A', color=cl[0], line_width=3)
r12 = p.line(source=source, x='x', y='B', color=cl[1], line_width=3)
p.xaxis.formatter=DatetimeTickFormatter(
seconds=["%H:%M:%S"],
minsec=["%H:%M:%S"],
minutes=["%H:%M:%S"],
hourmin=["%H:%M:%S"],
hours=["%H:%M:%S"],
days=["%H:%M:%S"],
months=["%H:%M:%S"],
years=["%H:%M:%S"],
)
p.y_range.start = -100
p.x_range.range_padding = 0.1
p.yaxis[0].ticker.desired_num_ticks = 5
p.xaxis.major_label_orientation = math.pi/2
show(p)
and here is the result:
As stated in the docs, num_desired_ticks is only a suggestion. If you want a ticks at specific locations that do not change, then you can use a FixedTicker, which can be set by plain list as convenience:
p.xaxis.ticker = [2, 3.5, 4]
For datetimes, you would pass the values as milliseconds since epoch.
If you want a fixed number of ticks, but the locations may change (i.e. because the range may change), then there is nothing built in to do that. You could make a custom ticker extension.
I am trying to plot 3 level category in bokeh python, but I am facing issue and my plot is not working, Could you give me a hand on this ?
below is my code :
from bokeh.models import ColumnDataSource, FactorRange
from bokeh.io import show
p = figure(plot_height=250, title="Fruit Counts by Year")
#tp is my data as pandas DataFrame
x1=list(tp['SRN'])
x2=list(tp['SN'])
x3=list(tp['PN'])
counts = list(tp['VS.FEGE.RXMAXSPEED'])
x=[("SRN"+str(a1),"SN"+str(a2),"pN"+str(a3)) for a1,a2,a3 in zip(x1,x2,x3)]
source = ColumnDataSource(data=dict(x=x, counts=counts))
p =figure(x_range=FactorRange(*x),plot_height=250,title="title",toolbar_location=None, tools="")
p.vbar(x='x', top='counts', width=0.9,source=source)
show(p)
tp data is as below :
SRN SN PN VS.FEGE.RXMAXSPEED VS.FEGE.TXMAXSPEED
0 0 18 0 1.794 0.307
1 0 18 1 1.896 0.307
2 0 19 0 131238.122 574793.502
3 0 19 1 31806.984 126149.078
4 0 20 0 4.968 0.307
I am not receiving any specific error, just plot is not shown.
Also I want to know how "x_range=FactorRange(*x)" is working and is there any alernative way instead ?
From the looks of it, you have not imported Bokeh.Plotting which defines the figure.
from bokeh.models import ColumnDataSource, FactorRange
from bokeh.io import show
from bokeh import plotting
p = plotting.figure(plot_height=250, title="Fruit Counts by Year")
Call "figure" function like this whereever you are using it. It will resolve the "Figure not defined" error which is occurring in your case.
I found the issue,
it is due to that x_range key argument is defined again in figure, if x_range is referenced later based on data, it will solve the issue :
p =figure(x_range=FactorRange(*x),plot_height=250,title="title",toolbar_location=e, tools="")
p.x_range.factors = x
I'm trying to use bokeh in python for interactive analysis of my plots.
My data are stored in pandas.Dataframe. I'd like to have a legend with column names as labels. However, bokeh extracts values from respective column instead.
import pandas as pd
from bokeh.plotting import figure
from bokeh.io import output_notebook, show
from bokeh.models import ColumnDataSource
output_notebook()
BokehJS 0.12.13 successfully loaded.
df = pd.DataFrame({'accuracy': np.random.random(10)}, index=pd.Index(np.arange(10), name='iteration'))
df
output:
accuracy
iteration
0 0.977427
1 0.057319
2 0.307741
3 0.127390
4 0.662976
5 0.313618
6 0.214040
7 0.214274
8 0.864432
9 0.800101
Now plot:
p = figure(width=900, y_axis_type="log")
source = ColumnDataSource(df)
p.line(x='iteration', y='accuracy', source=source, legend='accuracy')
show(p)
Result:
Desired output, obtained with adding space: legend='accuracy'+' ':
Although I've reached my goal, the method does not satisfy me. I think, there should be more elegant and official way to tell between column name and legend label.
There is. Bokeh tries to "do the right thing" in most situations, but doing that makes for a few corner cases where the behavior is less desirable, and this is one of them. However, specifically in this instance, you can always be explicit about whether the string is to be interpreted as a value or as field:
from bokeh.core.properties import value
p.line(x='iteration', y='accuracy', source=source, legend=value('accuracy'))
I have a dataframe of multiple columns. First two columns are x and y coordinates and the rest columns are different property values for (x,y) pairs.
import pandas as pd
import numpy as np
df = pd.DataFrame()
df['x'] = np.random.randint(1,1000,100)
df['y'] = np.random.randint(1,1000,100)
df['val1'] = np.random.randint(1,1000,100)
df['val2'] = np.random.randint(1,1000,100)
df['val3'] = np.random.randint(1,1000,100)
print df.head()
x y val1 val2 val3
0 337 794 449 969 933
1 19 563 592 677 886
2 512 467 664 160 16
3 36 112 91 230 910
4 972 572 336 879 860
Using customJS in Bokeh, I would like to change the value of color in 2-D heatmap by providing a drop down menu.
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.models import LinearColorMapper
from bokeh.palettes import RdYlBu11 as palette
p = figure()
source = ColumnDataSource(df)
color_mapper = LinearColorMapper(palette=palette)
p.patches('x', 'y', source=source,\
fill_color={'field': 'val1', 'transform':color_mapper})
show(p)
The above commands plot a color map whose color is determined by the column 'val1'. I would like to plot different columns (either val1, val2, or val3) based on whatever is selected in the drop down menu.
I can create a drop down widget in bokeh by doing
from bokeh.models.widgets import Select
select = Select(title="Option:", value="val1", options=["val1","val2","val3"])
But, I'm not quite sure how I can use the selected value to update the plot by using callback.
Could someone give me a guideline here?
Thanks.
I have included an example with comments inline with the code. The main important steps are to write the javascript code that is executed each time the selected option on the widget changes. The code simply needs to just re-assign which of the columns is set to the values for the 'y' column of the datasource.
The other issue is that your data is just x and y points. The patches glyph will require a different data structure which defines the boundaries of the patches. I believe there are better ways to make a heatmap in bokeh, there should be numerous examples on stack overflow and the bokeh docs.
import pandas as pd
import numpy as np
from bokeh.io import show
from bokeh.layouts import widgetbox,row
from bokeh.models import ColumnDataSource, CustomJS
df = pd.DataFrame()
df['x'] = np.random.randint(1,1000,1000)
df['y'] = np.random.randint(1,1000,1000)
df['val1'] = np.random.randint(1,1000,1000)
df['val2'] = np.random.randint(1,1000,1000)
df['val3'] = np.random.randint(1,1000,1000)
from bokeh.plotting import figure
from bokeh.models import LinearColorMapper
from bokeh.palettes import RdYlBu11 as palette
p = figure(x_range=(0,1000),y_range=(0,1000))
source = ColumnDataSource(df)
source_orig = ColumnDataSource(df)
color_mapper = LinearColorMapper(palette=palette)
p.rect('x', 'y', source=source,width=4,height=4,
color={'field': 'val1', 'transform':color_mapper})
from bokeh.models.widgets import Select
select = Select(title="Option:", value="val1", options=["val1","val2","val3"])
callback = CustomJS(args={'source':source},code="""
// print the selectd value of the select widget -
// this is printed in the browser console.
// cb_obj is the callback object, in this case the select
// widget. cb_obj.value is the selected value.
console.log(' changed selected option', cb_obj.value);
// create a new variable for the data of the column data source
// this is linked to the plot
var data = source.data;
// allocate the selected column to the field for the y values
data['y'] = data[cb_obj.value];
// register the change - this is required to process the change in
// the y values
source.change.emit();
""")
# Add the callback to the select widget.
# This executes each time the selected option changes
select.callback = callback
show(row(p,select))
I would like to create a plot with 2 y-axes, whose ranges are being updated on a button click. The script would run on a Bokeh server. Note that in the code below, the primary y-axis is being updated by changing f.y_range.start/end. However, this is not possible with the secondary y-axis. I tried two other commands instead, i.e.
f.extra_y_ranges.update({"y2Range": Range1d(start=0, end=50)})
and
f.extra_y_ranges.update = {"y2Range": Range1d(start=0, end=50)}
But none of them work.
A similar questions was asked here: Bokeh: How to change extra axis visibility
# Import libraries
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, Range1d, LinearAxis
from bokeh.models.widgets import Button
from bokeh.layouts import layout
from bokeh.plotting import figure
# Create figure
f=figure()
# Create ColumnDataSource
source = ColumnDataSource(dict(x=range(0,100),y=range(0,100)))
# Create Line
f.line(x='x',y='y',source=source)
f.extra_y_ranges = {"y2Range": Range1d(start=0, end=100)}
f.add_layout(LinearAxis(y_range_name='y2Range'), 'left')
# Update axis function
def update_axis():
f.y_range.start = 0
f.y_range.end = 50
# Create Button
button = Button(label='Set Axis')
# Update axis range on click
button.on_click(update_axis)
# Add elements to curdoc
lay_out=layout([[f, button]])
curdoc().add_root(lay_out)
I was facing a similar problem. I was able to update the range of the secondary axis by accessing it through the 'extra_y_axis' dict via the name I created it with. For your case, it should look something like:
# Update primary axis function
def update_axis():
f.y_range.start = 0
f.y_range.end = 50
# Update secondary axis function
def update_secondary_axis():
f.extra_y_ranges['y2Range'].start = -20 #new secondary axis min
f.extra_y_ranges['y2Range'].end = 80 #new secondary axis max