Is it possible to pass arguments (data) to a bokeh app?

Viewed 227

So I've got a variety of pandas dataframes and I'd like to reuse a bokeh app to view them, rather than hard coding the data into the app. I've adapted the crossfilter example to show the problem with my approach.

This should be called as

dframe = some Pandas dataframe
invokeh(bk_crossfilter, dframe)

and defined as in the code block below.

It seems that bokeh doesn't like the use of functools.partial in the definition of the app below and gives me a 500 server error in the browser. Earlier today the print statement in the bokeh app was showing the dataframe, but that now seems to get mangled into bokeh.Document during the function entry. The console output is now:

Preparing a bokeh application.
Opening Bokeh application on http://localhost:5006/
CROSSFILTER:  <bokeh.document.document.Document object at 0x0000029BAB2783C8>
ERROR:tornado.application:Uncaught exception GET / (::1)
HTTPServerRequest(protocol='http', host='localhost:5006', method='GET', uri='/', version='HTTP/1.1', remote_ip='::1')
Traceback (most recent call last):
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\site-packages\tornado\web.py", line 1703, in _execute
    result = await result
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\site-packages\bokeh\server\views\doc_handler.py", line 52, in get
    session = await self.get_session()
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\site-packages\bokeh\server\views\session_handler.py", line 120, in get_session
    session = await self.application_context.create_session_if_needed(session_id, self.request, token)
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\site-packages\bokeh\server\contexts.py", line 218, in create_session_if_needed
    self._application.initialize_document(doc)
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\site-packages\bokeh\application\application.py", line 171, in initialize_document
    h.modify_document(doc)
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\site-packages\bokeh\application\handlers\function.py", line 132, in modify_document
    self._func(doc)
  File "c:\users\jdorsey\code\utils\dorsey\visualisation.py", line 36, in bk_crossfilter
    columns = sorted(df.columns)
AttributeError: 'Document' object has no attribute 'columns'
ERROR:tornado.access:500 GET / (::1) 7.98ms
WARNING:tornado.access:404 GET /favicon.ico (::1) 0.99ms
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\users\jdorsey\code\utils\dorsey\visualisation.py", line 27, in invokeh
    io_loop.start()
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\site-packages\tornado\platform\asyncio.py", line 149, in start
    self.asyncio_loop.run_forever()
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\asyncio\base_events.py", line 422, in run_forever
    self._run_once()
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\asyncio\base_events.py", line 1396, in _run_once
    event_list = self._selector.select(timeout)
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\selectors.py", line 323, in select
    r, w, _ = self._select(self._readers, self._writers, [], timeout)
  File "C:\Users\jdorsey\code\conda\environment\REDACTED\lib\selectors.py", line 314, in _select
    r, w, x = select.select(r, w, w, timeout)

Adding a print statement to the invokeh function confirms dataset is a valid pandas frame there.

[1379 rows x 82 columns]

And the code:

from tornado.ioloop import IOLoop
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
from bokeh.server.server import Server
from bokeh.layouts import column, row
from bokeh.models import Select
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure
from functools import partial

def invokeh(visual,dataset):
    """
    Launch a bokeh server and connect to it.
    
    ARGUMENTS:
    visual: A bokeh visualisation
    dataset: A dataset to populate the visualisation
    """
    print("Preparing a bokeh application.")
    io_loop = IOLoop.current()
    bokeh_app = Application(FunctionHandler(partial(visual,dataset)))
    server = Server({"/": bokeh_app})#, io_loop=io_loop)
    server.start()
    print("Opening Bokeh application on http://localhost:5006/")
    
    io_loop.add_callback(server.show, "/")
    io_loop.start()

def bk_crossfilter(doc,df):
    print('CROSSFILTER: ', df)
    SIZES = list(range(6, 22, 3))
    COLORS = Spectral5
    N_SIZES = len(SIZES)
    N_COLORS = len(COLORS)
    
    columns = sorted(df.columns)
    discrete = [x for x in columns if df[x].dtype == object]
    continuous = [x for x in columns if x not in discrete]
    
    def create_figure():
        xs = df[x.value].values
        ys = df[y.value].values
        x_title = x.value.title()
        y_title = y.value.title()
        
        kw = dict()
        
        if x.value in discrete:
            kw['x_range'] = sorted(set(xs))
        if y.value in discrete:
            kw['y_range'] = sorted(set(ys))
        kw['title'] = "%s vs %s" % (x_title, y_title)
        
        p = figure(plot_height=600, plot_width=800, tools='pan,box_zoom,hover,reset', **kw)
        p.xaxis.axis_label = x_title
        p.yaxis.axis_label = y_title
        
        if x.value in discrete:
            p.xaxis.major_label_orientation = pd.np.pi / 4
        sz = 9
        
        if size.value != 'None':
            if len(set(df[size.value])) > N_SIZES:
                groups = pd.qcut(df[size.value].values, N_SIZES, duplicates='drop')
            else:
                groups = pd.Categorical(df[size.value])
            sz = [SIZES[xx] for xx in groups.codes]
        
        c = "#31AADE"
        
        if color.value != 'None':
            if len(set(df[color.value])) > N_COLORS:
                groups = pd.qcut(df[color.value].values, N_COLORS, duplicates='drop')
            else:
                groups = pd.Categorical(df[color.value])
            c = [COLORS[xx] for xx in groups.codes]
        
        p.circle(x=xs, y=ys, color=c, size=sz, line_color="white", alpha=0.6, hover_color='white', hover_alpha=0.5)
        
        return p
    
    def update(attr, old, new):
        layout.children[1] = create_figure()
    
    print("COLUMNS: ", columns[0], columns[1])
    print("DISCRETE: ", discrete, " CONTINUOUS: ", continuous)
    x = Select(title='X-Axis', value=columns[0], options=columns)
    x.on_change('value', update)
    
    y = Select(title='Y-Axis', value=columns[1], options=columns)
    y.on_change('value', update)
    
    size = Select(title='Size', value='None', options=['None'] + continuous)
    size.on_change('value', update)
    
    color = Select(title='Color', value='None', options=['None'] + continuous)
    color.on_change('value', update)
    
    controls = column(x, y, color, size, width=200)
    layout = row(controls, create_figure())
    
    curdoc().add_root(layout)
    curdoc().title = "Crossfilter"

Thanks in advance for any suggestions.

2 Answers

By changing :

bokeh_app = Application(FunctionHandler(partial(visual,dataset)))

to:

bokeh_app = Application(FunctionHandler(lambda dataset: visual))

I now only get this in the console:

Opening Bokeh application on http://localhost:5006/
WARNING:tornado.access:404 GET /favicon.ico (::1) 1.00ms

And no error in the browser. But the browser renders a blank page containing the following source (there's more but it doesn't tell me much):

root.Bokeh.embed.embed_items(docs_json, render_items);
            
              }
              if (root.Bokeh !== undefined) {
                embed_document(root);
              } else {
                var attempts = 0;
                var timer = setInterval(function(root) {
                  if (root.Bokeh !== undefined) {
                    clearInterval(timer);
                    embed_document(root);
                  } else {
                    attempts++;
                    if (attempts > 100) {
                      clearInterval(timer);
                      console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
                    }
                  }
                }, 10, root)
              }

The console log doesn't actually display the error message mentioned in the page source...

Pretty sure you need to be explicit with partial about which arguments you want bound to what:

partial(visual, df=dataset)

As it is I am nearly certain you are binding dataset to the doc argument, so when Bokeh calls your app function the Document gets passing in as df instead (the only free argument left).

Related