Select options update

Hi, I’m using the output of a different Select widget (let’s call it tableName) to upate the options of a Select Widget (let’s call it x-axis). These are used to update a plot. When I update widget tableName, the options of x-axis are updated; however, the shown value of the widget is updated to be the first element in the list of options (which I don’t want). Interestingly enough, the actual “value” of the select is unchanged as the plot itself only adjusts as a reflection of the updated Select (tableName), though it is distracting that the appearance of the Select (x-axis) no longer matches what is on the plot. Thanks in advance for any advice on how to resolve this. If anything is unclear, happy to clarify further.

HI @alexgrossman I can’t say I’ve seen anything like that. What is always best to focus and clarify discussion is actual code, specifically a Minimal, Reproducing Example that can actually be run to observe exactly what you are seeing.

Attaching a simple example to be run in jupyter.

    import pandas as pd
    import numpy as np

    testDf = pd.DataFrame(index=range(100))
    for j in range(10):
        testDf['col'+str(j)] = np.random.normal(0, j,(100,))
    testDf2 = pd.DataFrame(index=range(100))
    for j in range(11):
        testDf2['col'+str(j)] = np.random.normal(0, j,(100,))
    dfs = {'1': testDf, '2': testDf2}


    from bokeh.plotting import figure
    from bokeh.layouts import column, row
    from bokeh.models import ColumnDataSource, Select
    from bokeh.io import output_file, show, output_notebook
    output_notebook()
    def modify_doc(doc):

        currSrc = ColumnDataSource(dfs['1'])
        currSrc.data['x']=dfs['1']['col0']
        currSrc.data['y']=dfs['1']['col1']
        plot1 = figure()
        plot1.circle(x='x', y='y', source=currSrc)
        plot1.xaxis.axis_label = 'col0'
        plot1.yaxis.axis_label = 'col1'
        drop = Select(title='DF', options=['1', '2'], value='1')
        drop.on_change('value', lambda attr, old, new: update())
        drop1 = Select(title='X-Axis', options=list(dfs['1'].columns), value='col0')
        drop1.on_change('value', lambda attr, old, new: update())
        drop2 = Select(title='Y-Axis', options=list(dfs['1'].columns), value='col1')
        drop2.on_change('value', lambda attr, old, new: update())
        def update():
            curr = dfs[drop.value]
            drop1.options =[x for x in list(curr.columns) if not x in ['x', 'y']]
            drop2.options =[x for x in list(curr.columns) if not x in ['x', 'y']]
            curr['x'] = curr[drop1.value]
            curr['y']=curr[drop2.value]
            plot1.xaxis.axis_label = drop1.value
            plot1.yaxis.axis_label =  drop2.value
            currSrc.data=curr.to_dict('list')

        doc.add_root(row(column(drop,drop1, drop2), plot1))
    show(modify_doc)

Hope that helps, this issue only occurs if “drop” is changed and if the two df’s contain different columns. I was wondering if there is a way to resolve this such that I can set the old value in the update function.

Struggling to find a workaround because it doesn’t seem like the “value” is changing, just what is being shown. Can’t find a property for this within the documentation.

@alexgrossman

I don’t use Jupyter, so I ran your code under bokeh server.

With that caveat, your code works as expected for me. The graphs update properly when the dataframe and columns are interactively selected, and the axes pulldown widget states are consistent with the plot.

The only issue I observe, related to your comment, is when col10 is selected for one of the axes with DF2 selected and then you go to switch to DF1. What happens here is that your code throws an error, and consequently the corresponding axis select widget reverts to its default value corresponding to the first index. I think you’ll need to add logic to the callback to address what you want to happen when a specific column is not available for the active dataframe.

@alexgrossman

Something like the following modifications to your minimal reproducible example avoids the above-mentioned problem. As mentioned in the inline comments, you can add state, e.g. variables to keep track of the selected axes associated with each dataframe so that these don’t revert to col0 and col1 as done here…

drop = Select(title='DF', options=['1', '2'], value='1')
drop.on_change('value', lambda attr, old, new: update_df())
drop1 = Select(title='X-Axis', options=list(dfs['1'].columns), value='col0')
drop1.on_change('value', lambda attr, old, new: update())
drop2 = Select(title='Y-Axis', options=list(dfs['1'].columns), value='col1')
drop2.on_change('value', lambda attr, old, new: update())

def update_df():
    curr = dfs[drop.value]
    drop1.options =[x for x in list(curr.columns) if not x in ['x', 'y']]
    drop2.options =[x for x in list(curr.columns) if not x in ['x', 'y']]
    # Reset drop1/2 values ... add other code if you want to preserve state for selected dataframes
    drop1.value = 'col0'
    drop2.value = 'col1'
    
def update():
    curr = dfs[drop.value]
    #drop1.options =[x for x in list(curr.columns) if not x in ['x', 'y']]
    #drop2.options =[x for x in list(curr.columns) if not x in ['x', 'y']]
    curr['x'] = curr[drop1.value]
    curr['y'] = curr[drop2.value]
    plot1.xaxis.axis_label = drop1.value
    plot1.yaxis.axis_label =  drop2.value
    currSrc.data=curr.to_dict('list')

I ran it on Bokeh server as well and got the same issue. Of course I am aware that the code will have an error in that case and I have code that covers that in my actual code (I just made this toy example for this purpose). I still am having the issue that both drop1 and drop2 reset themselves to display “col0” when a different dataframe is selected. I think you may haev misunderstood in that code that you sent, my goal is to not have the values of drop1 and 2 reset themselves but to stay the same and to have the widgets display the values as well, rather than “col0” when that isn’t the value. I can post my bokeh server code if needed.

@alexgrossman

I believe I understood the statement correctly. My observation with your minimal reproducible example is that the axes in the widgets were properly displayed, i.e. consistent with the plots.

The only time that wasn’t the case was when col10 was selected for DF2 and the user switched to DF1. In that specific case, the axis (or axes) with col10 selected showed col0 in the widget b/c DF1 has no col10.

The code I sent was just a simple way to avoid the error condition without adding state variables to do something different. In the code excerpt I included, it is true that I reset the columns, but the values of the widgets and the plots are consistent.

So when you switch from DF1 to DF2 with, for example, “col6” and “col5” as the axes, it doesn’t switch to “col0” and “col0” on the widget texts while remaining the same on the plots? What version of bokeh are you using? Mine definitely switches on both the Bokeh server and the jupyter notebook.

Correct.

I have multiple conda environments. I just ran with bokeh 1.4.0 in one environment and 2.0.2 in another, and things behaved similarly.

I’m using 2.1.1 and am definitely seeing this issue. @Bryan could you give any more information on what this could be?

@alexgrossman

I see. I had to hold off on upgrading to 2.1.1 b/c of a bug that directly affects one of my main apps that is scheduled for a fix in the next release.

I can create another environment soon and run with 2.1.1 to see if it is a regression/bug that I can reproduce.

Ok thanks. Just trying to confirm that I’m not going crazy. It of course doesn’t affect the functionality of the bokeh app but is quite distracting and almost unusable to any end user for that reason.

@alexgrossman

Confirming your sanity.

There is a definite change in behavior between bokeh 2.0.2 and 2.1.1 for your minimum reproducible example.

Under bokeh 2.0.2, the widgets and plots remain consistent when interactively changing the selected dataframe.

Under bokeh 2.1.1, the plot reflects the selected columns for the active dataframes; however, the axes selected widgets revert to col0 in their displays and are thus inconsistent with what is actually plotted per your report of the behavior.

Got it. Thanks so much. That helps a lot. I will try downgrading my version of bokeh and it seems that should work then. Do you know if there are any large changes between 2.0.2 and 2.1.1 that I should know about that may give me different errors?

@alexgrossman

Just narrowed it down a bit further, and the issue also exists in 2.1.0, so that should help isolate the change in behavior as being introduced in the upgrade from 2.0.2 to 2.1.0.

The bugfix that I am waiting for is under this issue. It is fairly specialized to using interpolators to constrain behaviors in plots.

https://discourse.bokeh.org/t/bokeh-plotting-changes-between-2-0-2-and-2-1-x/6004

Bokeh has a detailed list of what goes into each release, separated into bugfixes, features, tasks, etc. See the release summary here https://docs.bokeh.org/en/latest/docs/releases.html. At that URL, there are also links for detailed changelogs.

If there are new capabilities of 2.1.x or the imminent 2.2, you can try to workaround the problem by separating your update() function into separate callbacks for the different widgets in case there is some race condition that is causing the unexpected behavior when they all go through the same function.

Ultimately, it is probably worthwhile to submit a GitHub issue with your example and a reference to this topic so that it can be fixed in a future minor or micro release.

Ok great. Thank you so much for all of your help. I really appreciate it.

1 Like

I haven’t followed this thread, there’s unfortunately alot of demands on my attention this month. But if @_jm thinks there is a bug / unintentional behaviour change, then an issue condensed with code and summary is definitely warranted. In cases like this if you can make a screen capture gif to attach to the issue, that can also really help communicate very efficiently .