Код не распознает ColumnDataSource для того, что это такое - PullRequest
0 голосов
/ 10 января 2020

Я хочу изменить источник данных простого линейного графика в зависимости от того, что пользователь выбирает из выпадающего меню.

У меня есть 2 кадра данных, вес и возраст меня и моего парня.

my_weight = [60,65,70] 
my_age = [21,22,25]

d_weight = [65,70,80] 
d_age = [21,22,25]

me = pd.DataFrame(list(zip(my_weight, my_age)), 
               columns =['weight', 'age'], index=None) 

dillon = pd.DataFrame(list(zip(d_weight, d_age)), 
               columns =['weight', 'age'], index=None)

Я превращаю эти два кадра данных в объекты ColumnDataSource, создаю свой график и линию, добавляю раскрывающийся список и jslink. Также есть демонстрационный слайдер, показывающий, как я могу изменить line_width моей линии.

from bokeh.models import ColumnDataSource
from bokeh.core.properties import Any, Bool, ColumnData

pn.extension()

source = ColumnDataSource(me, name="Me")
source2 = ColumnDataSource(dillon, name="Dillon")
# print("Me: ", source.data, "Dillon: ", source2.data)

plot = figure(width=300, height=300)
myline = plot.line(x='weight', y='age', source=source, color="pink")

width_slider = pn.widgets.FloatSlider(name='Line Width', start=0.1, end=10)
width_slider.jslink(myline.glyph, value='line_width')

dropdown2 = pn.widgets.Select(name='Data', options=[source, source2])
dropdown2.jslink(myline, value='data_source')

pn.Column(dropdown2, width_slider, plot)

Когда я запускаю этот код, я получаю ошибку

ValueError: expected an instance of type DataSource, got ColumnDataSource(id='5489', ...) of type str

с ошибкой, возникающей в разделе кода dropdown2.

Что мешает коду распознавать source и source2 как объекты ColumnDataSource ()? Что означает полученный ColumnDataSource (id = '5489', ...) типа str ? Как это строка?

enter image description here

Ответы [ 2 ]

2 голосов
/ 11 января 2020

Здесь много проблем. Во-первых, виджет Select фактически не делает сложные объекты доступными в Javascript, поэтому обратные вызовы, которые пытаются получить доступ к этим моделям в обратном вызове JS, не будут работать. Поэтому единственное решение состоит в том, чтобы написать фактический обратный вызов JS и предоставить фактические модели как args. Вторичное осложнение заключается в том, что два источника данных содержат разные столбцы, в частности, «Me» ColumnDataSource содержит столбец возраста, а источник данных «Dillon» содержит столбец высоты. Это означает, что вам также необходимо обновить глиф, чтобы посмотреть на эти разные источники. На практике это выглядит так:

import panel as pn

from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, DataRange1d
from bokeh.core.properties import Any, Bool, ColumnData

source = ColumnDataSource(me, name="Me")
source2 = ColumnDataSource(dillon, name="Dillon")

plot = figure(width=300, height=300)
myline = plot.line(x='weight', y='age', source=source, color="pink")

width_slider = pn.widgets.FloatSlider(name='Line Width', start=1, end=10)
width_slider.jslink(myline.glyph, value='line_width')
dropdown2 = pn.widgets.Select(name='Data', options={'Me': source, 'Dillon': source2})

code = """
if (cb_obj.value == 'Me') {
  myline.data_source = source
  myline.glyph.y = {'field': 'age'}

} else {
  myline.data_source = source2
  myline.glyph.y = {'field': 'height'}
}
"""
dropdown2.jscallback(args={'myline': myline, 'source': source, 'source2': source2}, value=code)

При этом я рекомендовал бы реализовать это в Panel следующим образом:

dropdown2 = pn.widgets.Select(name='Data', options={'Me': me, 'Dillon': dillon}, value=me)
width_slider = pn.widgets.FloatSlider(name='Line Width', start=1, end=10)

@pn.depends(dropdown2)
def plot(data):
    source = ColumnDataSource(data)
    plot = figure(width=300, height=300)
    column = 'age' if 'age' in source.data else 'height'
    myline = plot.line(x='weight', y=column, source=source, color="pink")
    width_slider.jslink(myline.glyph, value='line_width')
    return plot

pn.Column(dropdown2, width_slider, plot).embed()

Наконец, если вы готовы дать hvPlot a go, это может быть дополнительно уменьшено до:

import hvplot.pandas

dropdown2 = pn.widgets.Select(name='Data', options={'Me': me, 'Dillon': dillon}, value=me)
width_slider = pn.widgets.FloatSlider(name='Line Width', start=1, end=10)

@pn.depends(dropdown2)
def plot(data):
    p = data.hvplot('weight', color='pink')
    width_slider.jslink(p, value='glyph.line_width')
    return p

pn.Column(dropdown2, width_slider, plot).embed()
0 голосов
/ 10 января 2020

Я не могу говорить с абстракциями Panel поверх Bokeh, но стоит упомянуть, что настоящие слайдеры Bokeh не могут иметь таких сложных вещей, как ColumnDataSource как значение options. Только простые типы, такие как числа, строки и т. Д. c. Как это происходит, это:

ColumnDataSource (id = '5489', ...)

Является "repr" для CDS, т.е. если вы пытаетесь распечатать CDS, что строка, которая генерируется. Поэтому я предполагаю, что Panel где-то конвертирует CDS в свое строковое представление и передает эту строку в базовый виджет Bokeh Select. Это не будет делать то, что вы хотите, конечно.

Возможно, существует лучший способ указать что-то для Panel c, но один из подходов - получить базовый виджет Bokeh Select, затем вызвать js_on_change, чтобы добавить CustomJS callback, который обновляет данные. В главе JavaScript Callbacks из документации вы можете эмулировать множество примеров.

...