Is it possible to show and update Pandas plots in Bokeh without using show()? Are there any examples of this online? I can't seem to find any. For example, something like:
def bar_plot(fig, source):
p = pd.DataFrame()
p = p.from_dict(source.data)
fig = p.plot.bar()
return fig
def update_data():
data = source.data
data['y'] = random.sample(range(0,100),len(data['y']))
source.data = data
button.on_click(update_data)
source = ColumnDataSource(data)
fig = bar_plot(fig, source)
layout = layout([[button,fig]])
curdoc().add_root(layout)
Pandas' built-in .plot method uses Matplotlib to generate images. The Bokeh server has no way of synchronizing or updating MPL plots across the Python/JS boundary. The Bokeh server can only show and update plots created using native Bokeh APIs (i.e. you can create a bar plot from your data frame using Figure.vbar or similar Bokeh functions).
Related
I am looking for a way to plot realtime data line plot or scatter plots from python.
With the plots I want to monitor long-running loops when experimenting with algorithms with scientific computing. I.e. to help me answer the question: Are my results still improving with each iteration or can I cancel the loop?
I am looking for a quick and dirty method. I saw that with Bokeh and Dash one can program dashboards with realtime updates, but an awful lot of boilerplate code seems to be required just to get an updating plot.
Here is a simple "live streaming" example for Bokeh v1.3.0. You can run it with bokeh serve --show app.py
app.py:
from bokeh.plotting import figure, curdoc
from datetime import datetime
import random
plot = figure(plot_width = 1200, x_axis_type = 'datetime', tools = 'pan,box_select,crosshair,reset,save,wheel_zoom')
line = plot.line(x = 'time', y = 'value', line_color = 'black', source = dict(time = [datetime.now()], value = [random.randint(5, 10)]))
def update():
line.data_source.stream(dict(time = [datetime.now()], value = [random.randint(5, 10)]))
curdoc().add_root(plot)
curdoc().add_periodic_callback(update, 1000)
I've been looking into using the Bokeh library to create animated data visualisations for some small projects I am working on. I feel that the gif format would be the best file format to export these visualisations in. It is widely used and they should be easy to share.
Is it possible (and advisable) to export bokeh animated plots in the gif format?
If so, will I need to make use of any additional tools to do this?
If not, is there a different file format that would be better suited to this?
I found this thread about potential options for creating gifs in Python, but I'm not sure if any of them are relevant to the Bokeh library. Programmatically generate video or animated GIF in Python?
Any help would be much appreciated. Many thanks.
Bokeh plot has a SaveTool which allows you to save the plot canvas manually in PNG format but this would be a lot of work for you to do. Alternatively you could automate this process by implementing Bokeh server app with update() function that updates the data_source property of your plot e.g. each second and saves a screenshot using export_png() function. Then you could use those images to build an animation e.g. using the Python lib you mentioned above.
This is an example script to run with bokeh serve --show app.py:
The content of app.py:
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from datetime import datetime
from bokeh.io import export_png
import random
source = ColumnDataSource(dict(time = [datetime.now()], value = [random.randint(5, 10)]))
plot = figure(plot_width = 1200, x_axis_type = 'datetime', tools = 'pan,box_select,crosshair,reset,save,wheel_zoom')
plot.line(x = 'time', y = 'value', line_color = 'black', source = source)
counter = 0
def update():
global counter
new_source_data = dict(time = [datetime.now()], value = [random.randint(5, 10)])
source.stream(new_source_data)
counter = counter + 1
export_png(plot, filename = "plot_" + str(counter) + ".png")
curdoc().add_root(plot)
curdoc().add_periodic_callback(update, 1000)
To make this script work you need to have phantomjs installed on your system. So first you need to install nodejs and npm, then install phantomjs like this:
sudo npm install -g phantomjs-prebuilt
If you are using Mac then another option is to use QuickTime Player screen recording to make a movie and then convert it into an animated gif using FFMPEG like explained in this post
I'm attempting to connect a datatable with a multiselect widget in bokeh. I've searched around and gathered that I need to develop a function to update the data source for the data table, but I seem to have two problems.
I cannot seem to access the value of the multiselect object after I click it.
I cannot seem to push the change to the notebook after receiving the change.
Here's an example of my code:
import pandas as pd
from bokeh.io import push_notebook
from bokeh.plotting import show, output_notebook
from bokeh.layouts import row
from bokeh.models.widgets import MultiSelect, DataTable, TableColumn
from bokeh.models import ColumnDataSource
output_notebook()
df=pd.DataFrame({'year':[2000,2001,2000,2001,2000,2001,2000,2001],
'color':['red','blue','green','red','blue','green','red','blue'],
'value':[ 0,1,2,3,4,5,6,7]})
columns=[TableColumn(field=x, title=x) for x in df.columns]
source=ColumnDataSource(df)
data_table=DataTable(source=source,columns=columns)
years=[2000,2001,2000,2001,2000,2001,2000,2001]
## MultiSelect won't let me store an integer value, so I convert them to strings
multi=MultiSelect(title="Select a Year", value=['2000','2001'],options=[str(y) for y in set(years)])
def update(attr,old, new):
yr=multi.value
yr_vals=[int(y) for y in yr]
new_data=df[df.year.isin(yr_vals)]
source.data=new_data
push_notebook(handle=t)
multi.on_change('value',update)
t=show(row(multi,data_table),notebook_handle=True)
push_notebook is uni-directional. That is, you can only push changes from the IPython kernel, to the JavaScript front end. No changes from the UI are propagated back to the running Python kernel. In other words, on_change is not useful (without more work, see below) If you want that kind of interaction, there are a few options:
Use ipywidgets with push_notebook. Typically this involved the interact function to automatically generate a simple UI with callbacks that use push_notebook to update the plots, etc. based on the widget values. Just to be clear, this approach uses ipywidgets, which are not Bokeh built-in widgets. You can see a full example notebook here:
https://github.com/bokeh/bokeh/blob/master/examples/howto/notebook_comms/Jupyter%20Interactors.ipynb
Embed a Bokeh server application. The Bokeh server is what makes it possible for on_change callbacks on Bokeh widgets to function. Typically this involves making a function that defines the app (by specifying how a new document is created):
def modify_doc(doc):
df = sea_surface_temperature.copy()
source = ColumnDataSource(data=df)
plot = figure(x_axis_type='datetime', y_range=(0, 25))
plot.line('time', 'temperature', source=source)
def callback(attr, old, new):
if new == 0:
data = df
else:
data = df.rolling('{0}D'.format(new)).mean()
source.data = ColumnDataSource(data=data).data
slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days")
slider.on_change('value', callback)
doc.add_root(column(slider, plot))
Then calling show on that function:
show(modify_doc)
A full example notebook is here:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
(Hacky option) some people have combined CustomJS callbacks with Jupyers JS function kernel.execute to propagate values back to the kernel.
I recently picked up learning bokeh and I am completely lost making callbacks work.
What I would like to do is update the source using the PointDrawTool. It does update the plot and the table, but apparently it does not update the renderer or the source. This has me seriously confused and I'd appreciate some help.
What I have working is as follows:
from bokeh.models.glyphs import Circle
from bokeh.plotting import figure, show, output_notebook, Column, Row
from bokeh import events
from bokeh.models import DataTable, TableColumn, PointDrawTool, ColumnDataSource, CustomJS
output_notebook()
p = figure(width = 400, height = 600)
source = ColumnDataSource({
'x': [38], 'y': [-12], 'color': ['red']
})
renderer = p.circle(x='x', y='y',
source=source,
color='color',
size=10)
columns = [TableColumn(field="x", title="x"),
TableColumn(field="y", title="y"),
TableColumn(field='color', title='color')]
table = DataTable(source=source, columns=columns, editable=True, height=200)
draw_tool = PointDrawTool(renderers=[renderer],
empty_value='red')
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool
show(Row(p,table))
Using your method of rendering the chart (show) it isn't possible to update the source for the chart (unless you write custom JavaScript to do so). In order to achieve this you would need to use the Bokeh server, as described here.
Basically, put all your code in a file called 'main.py', and then save this in a folder with your project name. Then in the terminal run
bokeh serve --show project_name
I haven't used the PointDrawTool before, but if it's a widget, you'll also need to write functions to program how the source data should be updated, using the on_click or on_change methods described here.
NOTE FROM BOKEH MAINTAINER: The MPL compatibility layer in Bokeh was deprecated and removed a long time ago. Nothing in this question is relevant to any recent or future versions of Bokeh.
I have a a violin plot created with mpl and bokeh which uses a dataframe as a data source. Example:
ax = sns.violinplot(x="Week of", y="Total Time",data=df, palette="muted", split=False, scale="count", inner="box", bw=0.1)
Then I create my plot with plot = mpl.to_bokeh()
The X axis are dates. I want to dynamically update this bokeh plot (the values being used on the x axis) when a user changes a slider widget. For example I have the slider:
beginSlider = Slider(start=0, end=10, value=1, step=.1, title="Start Date", callback=callback)
When the user changes the date on the slider I want the data source for the plot to be changed thus updating the dates on the xaxis of the plot. I am familiar with bokeh callbacks, however because the source for this plot is a dataframe and not a ColumnDataSource I can not figure out how to trigger a change for the data source/plot in a widget callback. Any thoughts?