Pass extra parameters to a python callback

I am trying to pass a pandas data frame as input parameters to a callback but I got “TypeError: ‘DataFrame’ objects are mutable, thus they cannot be hashed” - code snipped: gist:6f5f20a270d6ecb92afd22be22edfa83 · GitHub

from functools import partial
from bokeh.plotting import curdoc
from bokeh.models.widgets import Select
from bokeh.layouts import widgetbox
import pandas as pd

raw_data = {'var_1': ['a', 'b', 'c', 'd', 'e'], 
        'var_2': [1, 2, 3, 4, 5]}
df = pd.DataFrame(raw_data, columns = ['var_1', 'var_2'])


items = ['var_1','var_1']
select = Select(title="Select variable:", value=items[0], options=items)

def callback_select(df, old, new):
    print(df[new])    

select.on_change(df, partial(callback_select))

layout = widgetbox(select)
curdoc().add_root(layout)

The first argument to on_change is the attribute name and not some arbitrary value.

You already have df defined on top, why do you need to pass it as a parameter?

i hardcoded df in this example, in reality it comes from a netcdf-url given to bokeh server as an argument in the url.

Based on the data-source behind the netcdf-url, my plotting routine changes a little … so I was trying to store the bokeh code in different functions - and call the appropriate one based on the type of netcdf.

My plotting routine and its callbacks is working fine if outside a function - a temporary-demo looks like:

https://bokeh.epinux.com/test/app5?url=https://hyrax.epinux.com/opendap/SN99938.nc

but if I move the plotting code in a function that returns the cuurdoc … it will work for the first rendering, but then the callbacks will no longer have access to the elements defined in the code.

I am now thinking that maybe this approach is wrong and
I should simply add more logic to handle the different cases in a single plotting routine - without defining rendering functions based on the url content

@epifanio

One approach that works for this in general is to organize your data, methods, and callbacks as part of a class. Then, the callback will have access to the other properties of the class.

It works just the same way for modules or closures - no need for classes, at all.

If you use df from an outer scope, it will have the value that’s in the outer scope. It’s as simple as that. Meaning, if that callback is called after you populate df with the right data, that callback will have the right data.

@p-himik thanks for the pointer - eventually I managed to get this to work following your advice :slight_smile:

In my use case the data-frame is generated by combining xarray and pandas - the code reads an url pointing to a netcdf resource.
In such a context the resulting dataframe can be quite different based on the data structure, e.g.:

  • a netcdf containing a time series will generate a time-indexed dataframe
  • while time series profile will generate a multi-index data-frame (a vertical profile for each time-stamp)
    So I needed the plotting routine to behave differently based on the data-source structure.

I ended up in adding a method to check the df structure and creating different callbacks chosen and attached to the relative widget based on the df structure (e.g.: datetime tooltip in case of time-series, slider widget to switch timestamped vertical profiles in case of time-series-profile … label’s text based on netcdf attributes and so on …)