How do you filter data in a Callback function in order to display a filtered plot

I have a simple example problem where I have created a pd.DataFrame containing numbers in the x and y columns and a text string in the z column. The data is passed to a ColumnDataSource so the x and y columns can be plotted as circle glyphs on a scatter plot. In addition I have included two buttons within a ChecboxButtonGroup that will be used to filter the dataframe based on the values of the z column. One button will filter out rows where z is equal to Apple and the other will do the same for Orange. When unchecked, the buttons should unfiltered the data based on the values in z. Attached is my attempt at this problem, but it is clearly not correct. What is the best way to to attack this example problem.

from bokeh.models import ColumnDataSource, CustomJS, Button
from bokeh.models import CheckboxButtonGroup
from bokeh.plotting import Figure, output_file, show
from bokeh.layouts import column
import pandas as pd

output_file("js_on_change.html")

x = [1.2, 2.4, 0.8, 5.6, 12.1, 8.8]
y = [3.4, 1.1, 4.2, 6.6, 1.8, 12.1]
z = ['Apple', 'Apple', 'Orange', 'Orange', 'Orange', 'Orange']

data = dict(x=x, y=y, z=z)
source = ColumnDataSource(data)

stats_df = pd.DataFrame({'bar_one_stat': [False],
                         'bar_two_stat': [False],
                        })
stats = ColumnDataSource(stats_df)


plot = Figure(plot_width=400, plot_height=400)
plot.circle('x', 'y', source=source, size=8, line_alpha=0.6)

callback1 = CustomJS(args=dict(source=source), code="""
    var f = 3.0

    var count = counter.data;
    var c = count['counter']
    if (c[0]%2==1) f = -1*f;
    c[0]++
    counter.change.emit();
    
    var data = source.data;
    var x = data['x']
    var y = data['y']
    for (var i = 0; i < x.length; i++) {
        y[i] = y[i] + f
    }
    source.change.emit();
""")

LABELS = ["Apple", "Orange"]

checkbox_button_group = CheckboxButtonGroup(labels=LABELS, active=[])

callback3 = CustomJS(args=dict(group=checkbox_button_group,
                               source=source, stats=stats),
                               code="""
    // Create variables for start status of buttons
    var status = stats.data
    var bar_one = status['bar_one_stat'];
    var bar_two = status['bar_two_stat'];
    
    // Create variables for data
    var data = source.data;
    var x = data['x'];
    var y = data['y'];
    
    // Filter dataframe to re-plot data
    if (group.active.includes(0) & bar_one[0] == false) {
        var iter = 0
        for (var i = 0; i < x.length; i++) {
            if (z[i] == 'Apple') {
                x[iter] = x[i]
                y[iter] = y[i]
                z[iter] = z[i]
            }
        }
        bar_one[0] = true;
    }
    
    source.change.emit();
    stats.change.emit();
""")

checkbox_button_group.js_on_click(callback3)
layout = column(checkbox_button_group, plot)
show(layout)

This is what works for me creating a copy of the data source. I’m sure there are more elegant ways using CDSView.

from bokeh.models import ColumnDataSource, CustomJS, Button
from bokeh.models import CheckboxButtonGroup
from bokeh.plotting import Figure, output_file, show
from bokeh.layouts import column
import pandas as pd

output_file("js_on_change.html")

x = [1.2, 2.4, 0.8, 5.6, 12.1, 8.8]
y = [3.4, 1.1, 4.2, 6.6, 1.8, 12.1]
z = ['Apple', 'Apple', 'Orange', 'Orange', 'Orange', 'Orange']

source = ColumnDataSource(data = dict(x=x, y=y, z=z))
source_copy = ColumnDataSource(data = dict(x=x, y=y, z=z))

plot = Figure(plot_width=400, plot_height=400)
plot.circle('x', 'y', source=source, size=8, line_alpha=0.6)

callback = CustomJS(args=dict(source=source, source_copy = source_copy), code="""

    var active_labels = cb_obj.active.map((item) => cb_obj.labels[item])
    
    var d1 = source.data;
    var d2 = source_copy.data;

    function getAllIndexes(arr, values) {
        var indexes = [], i;
        for(i = 0; i < arr.length; i++)
        if (values.includes(arr[i]))
            indexes.push(i);
    return indexes;
    }
    
    var indexes = getAllIndexes(d2['z'], active_labels);
    
    d1['x'] = []
    d1['y'] = []
    d1['z'] = []
    
    for (var i = 0; i < indexes.length; i++) {           
        d1['x'].push(d2['x'][indexes[i]])
        d1['y'].push(d2['y'][indexes[i]])
        d1['z'].push(d2['z'][indexes[i]])           
    }
    
    source.change.emit()

""")

LABELS = ["Apple", "Orange"]

checkbox_button_group = CheckboxButtonGroup(labels=LABELS, active=[])

checkbox_button_group.js_on_click(callback)

layout = column(checkbox_button_group, plot)

show(layout)

Thank you, that was very helpful!