Quiero un CheckboxGroup para representar diferentes columnas de un marco de datos. La idea es que el usuario pueda agregar varios valores de columna si selecciona varias columnas y muestra de forma interactiva la suma como un gráfico.
Tengo la siguiente trama de una ayuda anterior que obtuve:
from bokeh.io import output_file, show from bokeh.plotting import figure from bokeh.layouts import layout, widgetbox from bokeh.models import ColumnDataSource, CustomJS from bokeh.models.widgets import CheckboxGroup import pandas as pd output_file('additive_checkbox.html') names = ['Number', 'A', 'B', 'C'] rows = [(340, 77, 70, 15), (341, 80, 73, 15), (342, 83, 76, 16), (343, 86, 78, 17)] data = pd.DataFrame(rows, columns=names) data['combined'] = None source = ColumnDataSource(data) callback = CustomJS(args=dict(source=source), code=""" const labels = cb_obj.labels; const active = cb_obj.active; const data = source.data; const sourceLen = data.combined.length; const combined=Array(sourceLen).fill(undefined); var combined_label = '' if (active.length > 0) { const selectedColumns = labels.filter((val, ind) => active.includes(ind)); for(let i = 0; i < sourceLen; i++) { let sum = 0; for(col of selectedColumns){ sum += data[col][i]; } combined[i] = sum; } // get index positions of active buttons; use that to retrieve labels to build "combined" label string for (let j=0; j < active.length; j++) { combined_label += labels[active[j]]+'+'; } combined_label = '('+combined_label.substring(0, combined_label.length - 1)+')'; } else { // if there are no active buttons, label as 'none' combined_label = '(none)'; } legend_item.label.value = 'combined '+combined_label; data.combined=combined; source.change.emit(); """) checkbox_group = CheckboxButtonGroup(labels=names[1:], active=[], width=400) checkbox_group.js_on_change("active", callback) final_col = column(p, checkbox_group) show(final_col) Y me está dando el siguiente error: ValueError: failed to validate CheckboxButtonGroup(id='1223', ...).js_event_callbacks: expected an element of Dict(String, List(Instance(CustomJS))), got CustomJS(id='1222', ...)
¡No soy bueno con JS y no tengo idea de lo que significa!
Actualización: ahora, después de frustrantes cambios de sintaxis que no estoy seguro de si son precisos, el error anterior desapareció, ¡pero no se trazan combinados ni ninguna columna de la fuente!
Aquí hay una solución para su problema usando líneas para trazar los datos.
La idea en la parte JS es inicializar para cada clic sus valores combined con 0 y luego recorrer todas las labels seleccionadas dadas a través de los valores active y agregarlos a combined .
Esto se hace en dos bucles for en cascada.
import pandas as pd from bokeh.plotting import figure, output_notebook, show from bokeh.layouts import column from bokeh.models import ColumnDataSource, CustomJS from bokeh.models.widgets import CheckboxButtonGroup output_notebook() # data names = ['Number', 'A', 'B', 'C'] rows = [(340, 77, 70, 15), (341, 80, 73, 15), (342, 83, 76, 16), (343, 86, 78, 17)] data = pd.DataFrame(rows, columns=names) data['combined'] = 0 source = ColumnDataSource(data) # figure p = figure(width=300, height=300, y_range=(-10, 200)) p.line(x='Number', y='A', color='red', source=source) p.line(x='Number', y='B', color='green', source=source) p.line(x='Number', y='C', color='blue', source=source) p.line(x='Number', y='combined', color='black', line_width=2, source=source) # JS callback = CustomJS(args=dict(source=source), code=""" const labels = cb_obj.labels; const active = cb_obj.active; const data = source.data; // initialize with 0 for(let i=0; i<data['Number'].length; i++) data['combined'][i] = 0 // add new value for every selected label for(let a of active){ let active_label = labels[a] for(let i=0; i<data['Number'].length; i++) data['combined'][i] += data[active_label][i] } source.change.emit(); """) checkbox_group = CheckboxButtonGroup(labels=names[1:], active=[], width=400) checkbox_group.js_on_change("active", callback) # output final_col = column(p, checkbox_group) show(final_col)Producción
Comentario
Para agregar una etiqueta de leyenda para cada renderizador line , legend_label="value" to p.line() .