首页 > 解决方案 > Ipywidgets 和 plotly 交互

问题描述

我正在尝试使用 plotly 与 ipywidgets 制作交互式情节,但恐怕我没有得到任何东西。我有一些带有坐标和一些列的数据框。我想在散点图中绘制数据框,以便 coord1=x, coord2=y 并且每个标记点都由交互选择的列选择的列的值着色。

此外,我希望当我使用交互式菜单更改列值时,每个点的颜色都会更改为我选择的列,并根据新列的最小值和最大值重新调整颜色条的最小值和最大值。此外,当我更改另一个选择器(selector2)时,我希望绘图仅显示与某个 colID big_grid [big_grid [“id_col”] == selector2.value] 匹配的mu 数据帧的子集。最后应该有一个范围滑块小部件来调整颜色条的颜色范围

所以现在我有了这个

big_grid=pd.DataFrame(data=dict(id_col=[1,2,3,4,5],
                         col1=[0.1,0.2,0.3,0.4,0.5],
                         col2=[10,20,30,40,50],
                         coord1=[6,7,8,9,10],
                         coord2=[6,7,8,9,10]))
list_elem=["col1","col2"]
list_id=big_grid.id_col.values

dropm_elem=widgets.Dropdown(options=list(list_elem)) 
dropm_id=widgets.SelectMultiple(
                options=list_id,
                description="Active",
                disabled=False
) 

rangewidg=widgets.FloatRangeSlider(value=[big_grid[dropm_elem.value].min(),big_grid[dropm_elem.value].max()],
                               min=big_grid[dropm_elem.value].min(),
                               max=big_grid[dropm_elem.value].max(),
                               step=0.001,
                               readout_format='.3f',
                               description="Color Range",
                              continuous_update=False)


fig = go.FigureWidget(data=px.scatter(big_grid, 
                                      x="coord1",
                                      y="coord2",
                                      color=big_grid[dropm_elem.value],
                                      color_continuous_scale="Turbo",)
                         )
def handle_id_change(change):
    fig.data[0]['x']=big_grid[big_grid['id_col'].isin(dropm_id.value)]["coord1"]
    fig.data[0]['y']=big_grid[big_grid['id_col'].isin(dropm_id.value)]["coord2"]
    fig.data[0]['marker']['color']=big_grid[big_grid['id_col'].isin(dropm_id.value)][dropm_elem.value]
    fig.data[0]['marker']['cmin']=big_grid[big_grid['id_col'].isin(dropm_id.value)][dropm_elem.value].min()
    fig.data[0]['marker']['cmax']=big_grid[big_grid['id_col'].isin(dropm_id.value)][dropm_elem.value].max()
    
def handle_elem_change(change):
    fig.data[0]['marker']['color']=big_grid[big_grid['id_col'].isin(dropm_id.value)][dropm_elem.value]   
   
dropm_elem.observe(handle_elem_change,names='value')
dropm_id.observe(handle_id_change,names='value')


right_box1 =widgets.HBox([fig])
right_box2=widgets.VBox([dropm_elem,dropm_id,rangewidg])
box=widgets.HBox([right_box1,right_box2])
box

因此,像这样选择子集(来自 dropm_id)是有效的,但是 rangewidget 和悬停被破坏了。基本上,当我更改 dromp_elem 时,颜色不会像我预期的那样调整,而是变暗且均匀。同时,如果您更改列并将鼠标悬停在点上,它会列出 col2 的值,但标签仍然显示 col1。

我担心我的生活过于复杂,肯定有更简单的方法,有人可以启发我吗?

编辑:如果我使用不同的方法并使用全局变量来定义要绘制的子集、绘图函数和 widget.interact 函数,我可以使其工作。问题是在这种情况下,绘图不是小部件,所以我不能将它放入 VBox 或 HBox。它也仍然感觉不对,使用全局变量不是很好的做法。无论如何我都会提供代码以供参考:

def plot(elem,rang):
    fig = px.scatter(subset, x="coord1", y="coord2", color=elem,color_continuous_scale="Turbo",range_color=rang)
    fig.show()

def handle_elem_change(change):
    with rangewidg.hold_trait_notifications():    #This is because if you do't put it it set max, 

        rangewidg.max=big_grid[dropm_elem.value].max() #and if max is < min he freaks out. Like this he first
        rangewidg.min=big_grid[dropm_elem.value].min() #set everything and then send the eventual errors notification.
        rangewidg.value=[big_grid[dropm_elem.value].min(),big_grid[dropm_elem.value].max()]

def handle_id_change(change):
    global subset
    subset=big_grid[big_grid['id_col'].isin(dropm_id.value)]

big_grid=pd.DataFrame(data=dict(id_col=[1,2,3,4,5],
                         col1=[0.1,0.2,0.3,0.4,0.5],
                         col2=[10,20,30,40,50],
                         coord1=[6,7,8,9,10],
                         coord2=[6,7,8,9,10]))
subset=big_grid
list_elem=["col1","col2"]
list_id=big_grid.id_col.values

dropm_elem=widgets.Dropdown(options=list(list_elem)) 
dropm_id=widgets.SelectMultiple(
                options=list_id,
                description="Active",
                disabled=False
) 

rangewidg=widgets.FloatRangeSlider(value=[big_grid[dropm_elem.value].min(),big_grid[dropm_elem.value].max()],
                               min=big_grid[dropm_elem.value].min(),
                               max=big_grid[dropm_elem.value].max(),
                               step=0.001,
                               readout_format='.3f',
                               description="Color Range",
                              continuous_update=False)


dropm_elem.observe(handle_elem_change,names='value')
dropm_id.observe(handle_id_change,names='value')

display(dropm_id)
widgets.interact(plot,elem=dropm_elem,rang=rangewidg) 

所以,我想要第二个代码的行为,但是在一个 widget.Hbox 中,并且可能不使用全局变量

标签: pythonpandasplotlyinteractiveipywidgets

解决方案


更新:我设法使用以下代码获得工作版本:

def handle_elem_change(change):
    with rangewidg.hold_trait_notifications():    #This is because if you do't put it it set max, 
        
        rangewidg.max=big_grid[dropm_elem.value].max() #and if max is < min he freaks out. Like this he first
        rangewidg.min=big_grid[dropm_elem.value].min() #set everything and then send the eventual errors notification.
        rangewidg.value=[big_grid[dropm_elem.value].min(),big_grid[dropm_elem.value].max()]

    
def plot_change(change):
    df=big_grid[big_grid['id_col'].isin(dropm_id.value)]
    output.clear_output(wait=True)
    with output:
            fig = px.scatter(df, x="coord1", y="coord2", color=dropm_elem.value,hover_data=["info"],
                 width=500,height=800, color_continuous_scale="Turbo",range_color=rangewidg.value)
            fig.show()
    
    
#define the widgets dropm_elem and rangewidg, which are the possible df.columns and the color range
#used in the function plot.
big_grid=pd.DataFrame(data=dict(id_col=[1,2,3,4,5],
                         col1=[0.1,0.2,0.3,0.4,0.5],
                         col2=[10,20,30,40,50],
                         coord1=[6,7,8,9,10],
                         coord2=[6,7,8,9,10],
                         info=["info1","info2","info3","info4","info5",]))
list_elem=["col1","col2","info"]
list_id=big_grid.id_col.values


dropm_elem=widgets.Dropdown(options=list_elem) #creates a widget dropdown with all the _ppms
dropm_id=widgets.SelectMultiple(
                options=list_id,
                description="Active Jobs",
                disabled=False
) 

rangewidg=widgets.FloatRangeSlider(value=[big_grid[dropm_elem.value].min(),big_grid[dropm_elem.value].max()],
                                   min=big_grid[dropm_elem.value].min(),
                                   max=big_grid[dropm_elem.value].max(),
                                   step=0.001,
                                   readout_format='.3f',
                                   description="Color Scale Range",
                                  continuous_update=False)
output=widgets.Output()
# this line is crucial, it basically says: Whenever you move the dropdown menu widget, call the function
# #handle_elem_change, which will in turn update the values of rangewidg
dropm_elem.observe(handle_elem_change,names='value')
dropm_elem.observe(plot_change,names='value')
dropm_id.observe(plot_change,names='value')
rangewidg.observe(plot_change,names='value')

# # #this line is also crucial, it links the widgets dropmenu and rangewidg with the function plot, assigning
# # #to elem and to rang (parameters of function plot) the values of dropmenu and rangewidg

left_box = widgets.VBox([output])
right_box =widgets.VBox([dropm_elem,rangewidg,dropm_id])
tbox=widgets.HBox([left_box,right_box]) 
# widgets.interact(plot,elem=dropm_elem,rang=rangewidg) 

display(tbox)

这样一切正常,但我基本上每次移动任何东西时都需要创建一个新的数据框。它对于大数据帧可能不是很有效,但它可以运行。


推荐阅读