Как ускорить обновление виджетов Select / MultiSelect в боке? - PullRequest
1 голос
/ 27 июня 2019

Я довольно новичок в bokeh и пытался взаимодействовать со своими построенными данными через виджеты Select и MultiSelect. Я заметил, что производительность значительно снижается, когда длинный аргумент (> 34 тыс. Элементов) предоставляется аргументу options для этих виджетов. Из того, что я видел, Select просто действует как mylist[mylist.index('selected_value')], а MultiSelect - как selected_vals.append(mylist[mylist.index('selected_value')]), поэтому, если это действительно так, я не понимаю, почему виджеты так медленно возвращают свое значение, так как python может легко обрабатывать эти операции со списками. любой длины. Хотя я работаю с многомерными большими наборами данных, я уверен, что загруженные данные на самом деле не играют роли, так как я уже пытался отделить обновления графиков от виджетов и просто распечатать возвращенное значение, выбранное из выпадающего меню (это может занять до 30 секунд, и даже открытое меню может занять несколько секунд). Я попытался сократить список, переданный аргументу параметров (без изменения размера загружаемых данных), и виджеты ведут себя как ожидалось. Есть ли обходной путь / исправить это? Вот часть кода

import numpy as np
import pandas as pd
from bokeh.layouts import row, column
from bokeh.models import Select, MultiSelect, ColumnDataSource, CDSView, GroupFilter
from bokeh.palettes import Viridis256
from bokeh.plotting import curdoc, figure
from bokeh.transform import log_cmap

data = np.random.rand(11000, 3)
normExpr = np.random.rand(11000, 35000)
gene_names = np.array(np.random.rand(35000), dtype='str')
clusters = list(np.array(range(20), dtype='str'))
normExpr = pd.DataFrame(normExpr, columns=gene_names)
data = pd.DataFrame(data, columns=['PHATE1', 'PHATE2', 'color'])
data['cluster'] = clusters*550
COLORS = Viridis256[::-1]
columns = list(gene_names)[:20]

def first_fig():
    s = slice(len(data['color']))
    source = ColumnDataSource(data)
    kw = dict()
    kw['title'] = "Expression of gene %s in %s" % (gene.value.title(), cluster.value.title())
    p = figure(plot_height=500, plot_width=550, tools='pan,box_zoom,hover,reset',
               toolbar_location="above", output_backend='webgl', **kw)
    p.background_fill_color="#fafafa"
    p.xaxis.axis_label = 'X-values'
    p.yaxis.axis_label = 'Y-values'
    if gene.value != 'None' and cluster.value != 'All tissue':
        view = CDSView(source=source, filters=[GroupFilter(column_name='cluster', group=cluster.value)])
        new = normExpr[gene.value]
        source.patch({'color': [(s, new)]})
        p.circle(x='PHATE1', y='PHATE2', source=source, view=view, radius=0.002, color=log_cmap('color', COLORS, 0, 1),
                 hover_color='white', hover_alpha=0.5)

    elif gene.value == 'None' and cluster.value != 'All tissue':
        view = CDSView(source=source, filters=[GroupFilter(column_name='cluster', group=cluster.value)])
        p.circle(x='PHATE1', y='PHATE2', source=source, view=view, radius=0.002, color="#31AADE",
                 hover_color='white', hover_alpha=0.5)
    elif gene.value != 'None' and cluster.value == 'All tissue':
        new = normExpr[gene.value]
        source.patch({'color': [(s, new)]})
        p.circle(x='PHATE1', y='PHATE2', source=source, radius=0.002, color=log_cmap('color', COLORS, 0, 1),
                 hover_color='white', hover_alpha=0.5)

    else:
        p.circle(x='PHATE1', y='PHATE2', source=source, radius=0.002, color='#31AADE',
                 hover_color='white', hover_alpha=0.5)
    return p


def update(attr, old, new):
    layout.children[1] = first_fig()


cluster = Select(title='Cluster', value='All tissue', options=['All tissue'] + clusters)
cluster.on_change('value', update)
gene = Select(title='Gene', value='None', options=['None'] + columns)
gene.on_change('value', update)


controls = column([cluster, gene], width=100)
layout = row(controls, first_fig())

curdoc().add_root(layout)
curdoc().title = "Gene mapping"

Ответы [ 2 ]

0 голосов
/ 01 июля 2019

Кажется, что проблема возникает только при использовании Microsoft Edge. Я изменил браузер на Chrome и сценарии работают нормально. Однако до сих пор не могу объяснить зависимость производительности от длины списка параметров, предоставляемых виджету select при использовании Edge.

0 голосов
/ 29 июня 2019
  • Прежде всего, следует подчеркнуть, что этот код выполняется полностью для каждого нового соединения. В частности, первый блок кода «Настройка данных» вверху занимает около 5 секунд для выполнения на моем ноутбуке. Bokeh не волшебство, оно не может заставить реальную работу процессора занимать меньше времени, чем требуется. Однако, если эти данные могут совместно использоваться (в идеале только для чтения) между сеансами, то вы можете использовать Lifecycle Hooks , чтобы эта инициализация происходила только один раз при запуске сервера. Вы можете увидеть конкретное использование этой техники в этом примере

  • Далее стоит указать общие рекомендации, которые всегда советуют:

    Всегда меняйте как можно меньше в обновлениях

    Следствие этого:

    Построить графики и макеты один раз, заранее , а позже изменить только данные .

    Это связано с тем, что Bokeh высоко оптимизирован для обработки обновлений данных (либо для столбцов в ColumnDataSource, либо для значений свойств существующих объектов). Когда вы воссоздаете все «с нуля», как в своей функции update, это крайне неэффективный способ использовать Bokeh. Другими словами, общий план приложения всегда лучше, если он выглядит примерно так:

    p = figure(...)
    
    view = GroupFilter(...)
    source = ColumnDataSource(...)
    
    # create all the glyphs you will *ever* need up front
    # can set unused ones .visible = False
    
    select = Select(...)
    
    def update(attr, old, new):
        # Update existing source or view here, toggle .visible on glyphs, etc
        # But don't make new objects
    
    curdoc().add_root(column(plot, select)
    
  • Наконец, начните с попытки выключить WebGL. Это в настоящее время без сопровождающего и имеет некоторые проблемы. Как правило, 11 тыс. Баллов должны находиться в пределах области стандартного рендеринга холста Bokeh, если только возможно много, много совпадений и альфа-композитинга. В этом случае вы можете рассмотреть Holoviews , которые могут объединить Bokeh и Datashader вместе.

К сожалению, для меня слишком сложно переписать весь ваш пример в соответствии с этими рекомендациями, но, надеюсь, они укажут вам правильное направление.

...