使用RangeSlider在熊猫数据框中选择列以交互方式更改bokeh中的点图

时间:2019-11-12 03:54:44

标签: python pandas bokeh

我有一个熊猫数据帧df,其中前两列代表x,y坐标,其余列代表时间片(t0,... tn),其中每个点每个点的存在(1)或不存在(0)时间片(ti)被记录。

我想使用RangeSlider(而不是Slider),以便我可以跨越一定范围的时间片并绘制出该范围内的点。

这是我到目前为止得到的,

from bokeh.layouts import column
from bokeh.plotting import figure, show
from bokeh.models import CustomJS, ColumnDataSource
from bokeh.models.widgets import RangeSlider

# pts is a dataframe with columns (x, y, t0, t1,...t19)
src = ColumnDataSource(data = pts) 

p = figure(plot_height = 500)
p.circle(source= src, x='x', y= 'y', size=2, color="navy", alpha=0.1)

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

    var data = source.data;
    // changed ti range
    var ti_start = cb.obj.value[0] + 2 //offset 
    var ti_end = cb.obj.value[1] + 2

    // change data (how to select columns???????) 
    data = data[ti_start:ti_end]

    source.change.emit()
""")
ti_slider = RangeSlider(start=0, end=19, value=(1,2), step=1, title="Time Period",
callback = callback)

layout = column(ti_slider, p)
show(layout)

上面的代码根本不起作用。将绘制点并显示RangeSlider,但是当我更改范围或滑行时什么也没有发生。我无法限制构成数据源(即数据框)的列。我尝试更改选择列的代码,但我不知道任何JavaScript。

这是我第一次尝试将CustomJS函数用于bokeh。

1 个答案:

答案 0 :(得分:0)

上面的代码中存在许多问题:

  • cb_obj而不是cb.obj
  • 使用现代的js_on_change,而不是非常老的临时callback参数
  • 您要分配给局部变量 data,然后丢弃结果-需要在某些时候从字面上分配给source.data,以确保效果。
  • 要通过更新数据源来实现此目的,您将需要两个数据源,该数据源始终具有您要提取的完整数据,而另一个则仅用于保存子集。如果只有一个数据源并对其进行子集化,那么现在您将丢弃永远无法取回的数据。 (未来的子集将针对当前子集,而不是整个子集)
  • 因此,最好为此使用CDSView,它使您可以表达可更新的子集视图以应用于恒定数据源。
  • JS没有类似Pandas的操作,您只需要执行所有嵌套循环即可检查每一行以确定子集索引
  • 只是猜测,但是如果您希望在滑块移动时保持相同的空间范围以进行比较,则可能需要固定x / y范围。

这是一个简化的工作示例:

from bokeh.layouts import column
from bokeh.plotting import figure, show
from bokeh.models import CustomJS, ColumnDataSource, RangeSlider, CDSView, IndexFilter

source = ColumnDataSource(data=dict(
    x=[1,2,3,4],
    y=[1,1,1,1],
    t0=[1,1,0,0],
    t1=[0,1,0,0],
    t2=[0,1,1,0],
    t3=[0,0,1,1],
    t4=[0,0,0,1],
    t5=[0,0,0,0],
))

p = figure(plot_height=500, x_range=(0,5), y_range=(0,2))

view = CDSView(source=source, filters=[IndexFilter([0, 1])])
p.circle('x', 'y', size=10, color="navy", alpha=0.8,
         source=source, view=view)

callback = CustomJS(args=dict(source=source, view=view), code="""
    const start = cb_obj.value[0]
    const end = cb_obj.value[1]
    const indices = []
    for (var i=0; i < source.get_length(); i++) {
        for (var j=start; j<=end; j++) {
            if (source.data["t" + j][i]==1) {
                indices.push(i)
                break
            }
        }
    }
    view.indices = indices
""")

ti_slider = RangeSlider(start=0, end=5, value=(0,1), step=1, title="Time Period")
ti_slider.js_on_change('value', callback)

show(column(ti_slider, p))

enter image description here